diff mbox

[ovs-dev,v2,1/2] nsh: rework NSH netlink keys and actions

Message ID 1503013138-13952-2-git-send-email-yi.y.yang@intel.com
State Changes Requested
Headers show

Commit Message

Yang, Yi Aug. 17, 2017, 11:38 p.m. UTC
Per kernel data path requirements, this patch changes OVS_KEY_ATTR_NSH
to nested attribute and adds three new NSH sub attribute keys:

    OVS_NSH_KEY_ATTR_BASE: for length-fixed NSH base header
    OVS_NSH_KEY_ATTR_MD1:  for length-fixed MD type 1 context
    OVS_NSH_KEY_ATTR_MD2:  for length-variable MD type 2 metadata

NSH match fields, set and PUSH_NSH action all use the below
nested attribute format:

OVS_KEY_ATTR_NSH begin
    OVS_NSH_KEY_ATTR_BASE
    OVS_NSH_KEY_ATTR_MD1
OVS_KEY_ATTR_NSH end

or

OVS_KEY_ATTR_NSH begin
    OVS_NSH_KEY_ATTR_BASE
    OVS_NSH_KEY_ATTR_MD2
OVS_KEY_ATTR_NSH end

In addition, NSH encap and decap actions are renamed as push_nsh
and pop_nsh to meet action naming convention.

Signed-off-by: Yi Yang <yi.y.yang@intel.com>
---
 datapath/linux/compat/include/linux/openvswitch.h |  57 +-
 include/openvswitch/nsh.h                         |  31 +-
 include/openvswitch/packets.h                     |  11 +-
 lib/dpif-netdev.c                                 |   4 +-
 lib/dpif.c                                        |   4 +-
 lib/flow.c                                        |  43 +-
 lib/match.c                                       |  12 +-
 lib/meta-flow.c                                   |  13 +-
 lib/nx-match.c                                    |   4 +-
 lib/odp-execute.c                                 |  75 ++-
 lib/odp-util.c                                    | 738 ++++++++++++++++++----
 lib/odp-util.h                                    |   4 +
 lib/packets.c                                     |  23 +-
 lib/packets.h                                     |   5 +-
 ofproto/ofproto-dpif-ipfix.c                      |   4 +-
 ofproto/ofproto-dpif-sflow.c                      |   4 +-
 ofproto/ofproto-dpif-xlate.c                      |  24 +-
 tests/nsh.at                                      |  28 +-
 18 files changed, 808 insertions(+), 276 deletions(-)

Comments

Jan Scheurich Aug. 18, 2017, 3:02 p.m. UTC | #1
Hi Yi,

I won't repeat my earlier general comment about OVS_NSH_KEY_ATTR_MD2.
Please find other specific comments below. Please also refer to the fixes I proposed in gitlab.

/Jan

> -----Original Message-----
> From: Yi Yang [mailto:yi.y.yang@intel.com]
> Sent: Friday, 18 August, 2017 01:39

> diff --git a/datapath/linux/compat/include/linux/openvswitch.h
> b/datapath/linux/compat/include/linux/openvswitch.h
> index bc6c94b..d7f9029 100644
> --- a/datapath/linux/compat/include/linux/openvswitch.h
> +++ b/datapath/linux/compat/include/linux/openvswitch.h

> +#define NSH_MD1_CONTEXT_SIZE 4
[Jan] This shouldn't be needed here and replaced by a macro defined in nsh.h

> +#define OVS_PUSH_NSH_MAX_MD_LEN 248
[Jan] This shouldn't be needed here and replaced by a macro defined in nsh.h

> diff --git a/include/openvswitch/nsh.h b/include/openvswitch/nsh.h
> index f4ccadc..ed9054e 100644
> --- a/include/openvswitch/nsh.h
> +++ b/include/openvswitch/nsh.h


>  struct nsh_md2_tlv {
> @@ -72,6 +72,8 @@ struct nsh_hdr {
>      };
>  };
>
> +#define NSH_M_TYPE2_MAX_LEN 256
[Jan] This is a duplicate definition (see below)


> +/* NSH MD Type 2 header maximum Length. */
> +#define NSH_M_TYPE2_MAX_LEN 256
[Jan] I suggest to rename this to NSH_HEADER_MAX_LEN as it is generic limit for any MD type, not only MD2, resulting from the fact that the length in 4 byte words is encoded in 6 bits.

Add a macro NSH_CONTEXT_HDRS_MAX_LEN 248 and use that instead of OVS_PUSH_NSH_MAX_MD_LEN.

>  static inline uint16_t
>  nsh_hdr_len(const struct nsh_hdr *nsh)
>  {
> -    return 4 * (ntohs(nsh->ver_flags_len) & NSH_LEN_MASK) >>
> NSH_LEN_SHIFT;
> +    return ((ntohs(nsh->ver_flags_len) & NSH_LEN_MASK) >>
> NSH_LEN_SHIFT) << 2;
[Jan] I personally find 4 * X clearer than X << 2 when the purpose really is to multiply by 4 rather than to shift left by two bits. The compiler should generate similar code with -O2 anyhow.

> diff --git a/include/openvswitch/packets.h b/include/openvswitch/packets.h
> index be91e02..5ee3099 100644
> --- a/include/openvswitch/packets.h
> +++ b/include/openvswitch/packets.h
> @@ -84,7 +84,16 @@ struct flow_nsh {
>      uint8_t np;
>      uint8_t si;
>      ovs_be32 spi;
> -    ovs_be32 c[4];
> +    ovs_be32 context[4];
> +};
> +
> +struct ovs_key_nsh {
> +    uint8_t flags;
> +    uint8_t mdtype;
> +    uint8_t np;
> +    uint8_t pad;
> +    ovs_be32 path_hdr;
> +    ovs_be32 context[4];
>  };

[Jan] I see in the second patch that you use this new struct as replacement for struct flow_nsh in struct flow. The main difference being that the new struct combines spi and si fields into the path_hdr. That is of course possible provided that the necessary conversions are made where needed, but I doubt that it improves readability in the code overall.

Was the main motivation to use the same data type both in struct flow as well as in the datapath for set(nsh) operations? Or did you want to prevent struct flow_nsh spilling over into 25 bytes as the spare octet used for the si field is now needed for the new ttl field?

I believe in the datapath the NSH key attributes could be parsed into an NSH header struct directly, no need to have an struct ovs_key_nsh.

> @@ -572,11 +574,12 @@ parse_nsh(const void **datap, size_t *sizep,
> struct flow_nsh *key)
>      switch (key->mdtype) {
>          case NSH_M_TYPE1:
>              for (size_t i = 0; i < 4; i++) {
> -                key->c[i] = get_16aligned_be32(&nsh->md1.c[i]);
> +                key->context[i] = get_16aligned_be32(&nsh->md1.context[i]);
>              }
>              break;
>          case NSH_M_TYPE2:
> -            /* Don't support MD type 2 yet, so return false */
> +            /* Don't support MD type 2 metedata parsing yet */
> +            break;
>          default:
>              return false;

[Jan] There is no need to return false here. If the md_type is unknown we don't parse the context headers, but we can still continue processing the NSH packet as the total length is all we need.

>      }
> @@ -885,9 +888,7 @@ miniflow_extract(struct dp_packet *packet, struct
> miniflow *dst)
>                                          sizeof(uint64_t));
>                  }
>                  else if (nsh.mdtype == NSH_M_TYPE2) {
> -                    /* parse_nsh has stopped it from arriving here for
> -                     * MD type 2, will add MD type 2 support code here later
> -                     */
> +                    /* Can't parse MD type 2 metedata yet */
>                  }

[Jan] If parse_nsh returns true, we always need to push the flow_nsh to miniflow. Please remove the extra check on MD1.

> diff --git a/lib/odp-execute.c b/lib/odp-execute.c
> index 5f4d23a..0a9a535 100644
> --- a/lib/odp-execute.c
> +++ b/lib/odp-execute.c
> @@ -273,19 +273,22 @@ odp_set_nd(struct dp_packet *packet, const
> struct ovs_key_nd *key,
>  /* Set the NSH header. Assumes the NSH header is present and matches
> the
>   * MD format of the key. The slow path must take case of that. */
>  static void
> -odp_set_nsh(struct dp_packet *packet, const struct ovs_key_nsh *key,
> -            const struct ovs_key_nsh *mask)
> +odp_set_nsh(struct dp_packet *packet, const struct flow_nsh *key,
> +            const struct flow_nsh *mask)
>  {
>      struct nsh_hdr *nsh = dp_packet_l3(packet);
> +    ovs_be32 path_hdr;
>
>      if (!mask) {
>          nsh->ver_flags_len = htons(key->flags << NSH_FLAGS_SHIFT) |
>                               (nsh->ver_flags_len & ~htons(NSH_FLAGS_MASK));
> -        put_16aligned_be32(&nsh->path_hdr, key->path_hdr);
> +        path_hdr = htonl((ntohl(key->spi) << NSH_SPI_SHIFT) |
> +                         key->si);
> +        put_16aligned_be32(&nsh->path_hdr, path_hdr);
>          switch (nsh->md_type) {
>              case NSH_M_TYPE1:
>                  for (int i = 0; i < 4; i++) {
> -                    put_16aligned_be32(&nsh->md1.c[i], key->c[i]);
> +                    put_16aligned_be32(&nsh->md1.context[i], key->context[i]);
>                  }
>                  break;
>              case NSH_M_TYPE2:
> @@ -300,16 +303,24 @@ odp_set_nsh(struct dp_packet *packet, const
> struct ovs_key_nsh *key,
>          nsh->ver_flags_len = htons(flags << NSH_FLAGS_SHIFT) |
>                               (nsh->ver_flags_len & ~htons(NSH_FLAGS_MASK));
>
> -        ovs_be32 path_hdr = get_16aligned_be32(&nsh->path_hdr);
> -        path_hdr = key->path_hdr | (path_hdr & ~mask->path_hdr);
> +        path_hdr = get_16aligned_be32(&nsh->path_hdr);
> +        uint32_t spi = (ntohl(path_hdr) & NSH_SPI_MASK) >> NSH_SPI_SHIFT;
> +        uint8_t si = (ntohl(path_hdr) & NSH_SI_MASK) >> NSH_SI_SHIFT;
> +        uint32_t spi_mask = ntohl(mask->spi);
> +        if (spi_mask == 0x00ffffff) {
> +            spi_mask = UINT32_MAX;
> +        }
> +        spi = ntohl(key->spi) | (spi & ~spi_mask);
> +        si = key->si | (si & ~mask->si);
> +        path_hdr = htonl((spi << NSH_SPI_SHIFT) | si);
>          put_16aligned_be32(&nsh->path_hdr, path_hdr);
>          switch (nsh->md_type) {
>              case NSH_M_TYPE1:
>                  for (int i = 0; i < 4; i++) {
> -                    ovs_be32 p = get_16aligned_be32(&nsh->md1.c[i]);
> -                    ovs_be32 k = key->c[i];
> -                    ovs_be32 m = mask->c[i];
> -                    put_16aligned_be32(&nsh->md1.c[i], k | (p & ~m));
> +                    ovs_be32 p = get_16aligned_be32(&nsh->md1.context[i]);
> +                    ovs_be32 k = key->context[i];
> +                    ovs_be32 m = mask->context[i];
> +                    put_16aligned_be32(&nsh->md1.context[i], k | (p & ~m));
>                  }
>                  break;
>              case NSH_M_TYPE2:

[Jan] The whole function could be simplified a lot when passing in a struct nsh_hdr instead of struct ovs_key_nsh

> @@ -345,9 +356,12 @@ odp_execute_set_action(struct dp_packet
> *packet, const struct nlattr *a)
>          odp_eth_set_addrs(packet, nl_attr_get(a), NULL);
>          break;
>
> -    case OVS_KEY_ATTR_NSH:
> -        odp_set_nsh(packet, nl_attr_get(a), NULL);
> +    case OVS_KEY_ATTR_NSH: {
> +        struct flow_nsh nsh;
> +        odp_nsh_key_from_attr(a, &nsh);

[Jan] Replace this with an  odp_nsh_hdr_from_attr that directly translates into struct nsh_hdr

> +        odp_set_nsh(packet, &nsh, NULL);
>          break;
> +    }
>
>      case OVS_KEY_ATTR_IPV4:
>          ipv4_key = nl_attr_get_unspec(a, sizeof(struct ovs_key_ipv4));
> @@ -473,10 +487,24 @@ odp_execute_masked_set_action(struct
> dp_packet *packet,
>                            get_mask(a, struct ovs_key_ethernet));
>          break;
>
> -    case OVS_KEY_ATTR_NSH:
> -        odp_set_nsh(packet, nl_attr_get(a),
> -                    get_mask(a, struct ovs_key_nsh));
> +    case OVS_KEY_ATTR_NSH: {
> +        struct flow_nsh nsh, nsh_mask;
> +        size_t size = nl_attr_get_size(a) / 2;
> +
> +        struct nlattr attr[1 + size / sizeof(struct nlattr) + 1];
> +        struct nlattr mask[1 + size / sizeof(struct nlattr) + 1];
> +
> +        mask->nla_type = attr->nla_type = nl_attr_type(a);
> +        mask->nla_len = attr->nla_len = NLA_HDRLEN + size;
> +        memcpy(attr + 1, (char *)(a + 1), size);
> +        memcpy(mask + 1, (char *)(a + 1) + size, size);

[Jan] This code looks very obscure. Can you replace this with something more readable and safe?

> +        odp_nsh_key_from_attr(attr, &nsh);
> +        odp_nsh_key_from_attr(mask, &nsh_mask);
> +        odp_set_nsh(packet, &nsh, &nsh_mask);
> +
>          break;
> +    }


> @@ -818,18 +846,21 @@ odp_execute_actions(void *dp, struct

> +        case OVS_ACTION_ATTR_PUSH_NSH: {
> +            uint8_t buffer[256];
> +            struct nsh_hdr *nsh_hdr = ALIGNED_CAST(struct nsh_hdr *,
> buffer);
> +            const struct nsh_hdr *nsh_hdr_src = nsh_hdr;
> +            odp_nsh_hdr_from_attr(nl_attr_get(a), nsh_hdr);

[Jan] Calling this function with nl_attr_get(a) as first argument seems to conflict with the implementation of function odp_nsh_hdr_from_attr(), which uses NL_NESTED_FOR_EACH() to iterate over the nested attributes. In my tests that caused crashes. Please double-check.

I have to stop here for today. I will hopefully continue review next week.

BR, Jan

> diff --git a/lib/odp-util.c b/lib/odp-util.c
> index 4f1499e..3e4403a 100644
> --- a/lib/odp-util.c
> +++ b/lib/odp-util.c
> @@ -129,8 +129,8 @@ odp_action_len(uint16_t type)
>      case OVS_ACTION_ATTR_PUSH_ETH: return sizeof(struct
> ovs_action_push_eth);
>      case OVS_ACTION_ATTR_POP_ETH: return 0;
>      case OVS_ACTION_ATTR_CLONE: return ATTR_LEN_VARIABLE;
> -    case OVS_ACTION_ATTR_ENCAP_NSH: return ATTR_LEN_VARIABLE;
> -    case OVS_ACTION_ATTR_DECAP_NSH: return 0;
> +    case OVS_ACTION_ATTR_PUSH_NSH: return ATTR_LEN_VARIABLE;
> +    case OVS_ACTION_ATTR_POP_NSH: return 0;
>
>      case OVS_ACTION_ATTR_UNSPEC:
>      case __OVS_ACTION_ATTR_MAX:
> @@ -264,7 +264,7 @@ format_nsh_key(struct ds *ds, const struct
> ovs_key_nsh *key)
>      switch (key->mdtype) {
>          case NSH_M_TYPE1:
>              for (int i = 0; i < 4; i++) {
> -                ds_put_format(ds, ",c%d=0x%x", i + 1, ntohl(key->c[i]));
> +                ds_put_format(ds, ",c%d=0x%x", i + 1, ntohl(key->context[i]));
>              }
>              break;
>          case NSH_M_TYPE2:
> @@ -334,41 +334,50 @@ format_nsh_key_mask(struct ds *ds, const struct
> ovs_key_nsh *key,
>          format_uint8_masked(ds, &first, "np", key->np, mask->np);
>          format_be32_masked(ds, &first, "spi", htonl(spi), htonl(spi_mask));
>          format_uint8_masked(ds, &first, "si", si, si_mask);
> -        format_be32_masked(ds, &first, "c1", key->c[0], mask->c[0]);
> -        format_be32_masked(ds, &first, "c2", key->c[1], mask->c[1]);
> -        format_be32_masked(ds, &first, "c3", key->c[2], mask->c[2]);
> -        format_be32_masked(ds, &first, "c4", key->c[3], mask->c[3]);
> +        format_be32_masked(ds, &first, "c1", key->context[0],
> +                           mask->context[0]);
> +        format_be32_masked(ds, &first, "c2", key->context[1],
> +                           mask->context[1]);
> +        format_be32_masked(ds, &first, "c3", key->context[2],
> +                           mask->context[2]);
> +        format_be32_masked(ds, &first, "c4", key->context[3],
> +                           mask->context[3]);
>      }
>  }
>
>  static void
> -format_odp_encap_nsh_action(struct ds *ds,
> -                            const struct ovs_action_encap_nsh *encap_nsh)
> +format_odp_push_nsh_action(struct ds *ds,
> +                           const struct nsh_hdr *nsh_hdr)
>   {
> -    uint32_t path_hdr = ntohl(encap_nsh->path_hdr);
> +    size_t mdlen = (((ntohs(nsh_hdr->ver_flags_len) & NSH_LEN_MASK)
> +                         >> NSH_LEN_SHIFT) << 2) - NSH_BASE_HDR_LEN;
> +    uint32_t path_hdr = ntohl(get_16aligned_be32(&nsh_hdr->path_hdr));
>      uint32_t spi = (path_hdr & NSH_SPI_MASK) >> NSH_SPI_SHIFT;
>      uint8_t si = (path_hdr & NSH_SI_MASK) >> NSH_SI_SHIFT;
> +    uint8_t flags = (ntohs(nsh_hdr->ver_flags_len) & NSH_FLAGS_MASK)
> +                        >> NSH_FLAGS_SHIFT;
>
> -    ds_put_cstr(ds, "encap_nsh(");
> -    ds_put_format(ds, "flags=%d", encap_nsh->flags);
> -    ds_put_format(ds, ",mdtype=%d", encap_nsh->mdtype);
> -    ds_put_format(ds, ",np=%d", encap_nsh->np);
> +    ds_put_cstr(ds, "push_nsh(");
> +    ds_put_format(ds, "flags=%d", flags);
> +    ds_put_format(ds, ",mdtype=%d", nsh_hdr->md_type);
> +    ds_put_format(ds, ",np=%d", nsh_hdr->next_proto);
>      ds_put_format(ds, ",spi=0x%x", spi);
>      ds_put_format(ds, ",si=%d", si);
> -    switch (encap_nsh->mdtype) {
> +    switch (nsh_hdr->md_type) {
>      case NSH_M_TYPE1: {
> -        struct nsh_md1_ctx *md1_ctx =
> -            ALIGNED_CAST(struct nsh_md1_ctx *, encap_nsh->metadata);
> +        const struct nsh_md1_ctx *md1_ctx = NSH_MD1_CTX(nsh_hdr);
>          for (int i = 0; i < 4; i++) {
>              ds_put_format(ds, ",c%d=0x%x", i + 1,
> -                          ntohl(get_16aligned_be32(&md1_ctx->c[i])));
> +                          ntohl(get_16aligned_be32(&md1_ctx->context[i])));
>          }
>          break;
>      }
> -    case NSH_M_TYPE2:
> +    case NSH_M_TYPE2: {
> +        const struct nsh_md2_tlv *md2_ctx = NSH_MD2_CTX(nsh_hdr);
>          ds_put_cstr(ds, ",md2=");
> -        ds_put_hex(ds, encap_nsh->metadata, encap_nsh->mdlen);
> +        ds_put_hex(ds, md2_ctx, mdlen);
>          break;
> +    }
>      default:
>          OVS_NOT_REACHED();
>      }
> @@ -1057,11 +1066,16 @@ format_odp_action(struct ds *ds, const struct
> nlattr *a,
>      case OVS_ACTION_ATTR_CLONE:
>          format_odp_clone_action(ds, a, portno_names);
>          break;
> -    case OVS_ACTION_ATTR_ENCAP_NSH:
> -        format_odp_encap_nsh_action(ds, nl_attr_get(a));
> +    case OVS_ACTION_ATTR_PUSH_NSH: {
> +        uint8_t buffer[256];
> +        struct nsh_hdr *nsh_hdr = ALIGNED_CAST(struct nsh_hdr *, buffer);
> +        const struct nsh_hdr *nsh_hdr_src = nsh_hdr;
> +        odp_nsh_hdr_from_attr(nl_attr_get(a), nsh_hdr);
> +        format_odp_push_nsh_action(ds, nsh_hdr_src);
>          break;
> -    case OVS_ACTION_ATTR_DECAP_NSH:
> -        ds_put_cstr(ds, "decap_nsh()");
> +    }
> +    case OVS_ACTION_ATTR_POP_NSH:
> +        ds_put_cstr(ds, "pop_nsh()");
>          break;
>      case OVS_ACTION_ATTR_UNSPEC:
>      case __OVS_ACTION_ATTR_MAX:
> @@ -1780,27 +1794,74 @@ find_end:
>      return s - s_;
>  }
>
> +static void
> +nsh_key_to_attr(struct ofpbuf *buf, const struct flow_nsh *nsh,
> +                uint8_t * metadata, size_t md_size,
> +                bool is_mask)
> +{
> +    size_t nsh_key_ofs;
> +    struct ovs_nsh_key_base base;
> +    struct ovs_nsh_key_md1 md1;
> +
> +    base.flags = nsh->flags;
> +    base.mdtype = nsh->mdtype;
> +    base.np = nsh->np;
> +    base.path_hdr = htonl((ntohl(nsh->spi) << NSH_SPI_SHIFT) |
> +                          nsh->si);
> +
> +    nsh_key_ofs = nl_msg_start_nested(buf, OVS_KEY_ATTR_NSH);
> +    nl_msg_put_unspec(buf, OVS_NSH_KEY_ATTR_BASE, &base, sizeof
> base);
> +
> +    if (is_mask) {
> +        for (int i = 0; i < 4; i++) {
> +            md1.context[i] = nsh->context[i];
> +        }
> +        nl_msg_put_unspec(buf, OVS_NSH_KEY_ATTR_MD1, &md1, sizeof
> md1);
> +    } else {
> +        switch (nsh->mdtype) {
> +        case NSH_M_TYPE1:
> +            for (int i = 0; i < 4; i++) {
> +                md1.context[i] = nsh->context[i];
> +            }
> +            nl_msg_put_unspec(buf, OVS_NSH_KEY_ATTR_MD1, &md1, sizeof
> md1);
> +            break;
> +        case NSH_M_TYPE2:
> +            if (metadata && md_size > 0) {
> +                nl_msg_put_unspec(buf, OVS_NSH_KEY_ATTR_MD2, metadata,
> +                                  md_size);
> +            }
> +            break;
> +        default:
> +            /* No match support for other MD formats yet. */
> +            break;
> +        }
> +    }
> +    nl_msg_end_nested(buf, nsh_key_ofs);
> +}
> +
> +
>  static int
> -parse_odp_encap_nsh_action(const char *s, struct ofpbuf *actions)
> +parse_odp_push_nsh_action(const char *s, struct ofpbuf *actions)
>  {
>      int n = 0;
>      int ret = 0;
> -    struct ovs_action_encap_nsh encap_nsh;
> -    uint32_t spi;
> -    uint8_t si;
>      uint32_t cd;
> +    struct flow_nsh nsh;
> +    uint8_t *metadata = NULL;
> +    uint8_t md_size = 0;
>
> -    if (!ovs_scan_len(s, &n, "encap_nsh(")) {
> +    if (!ovs_scan_len(s, &n, "push_nsh(")) {
>          ret = -EINVAL;
>          goto out;
>      }
>
>      /* The default is NSH_M_TYPE1 */
> -    encap_nsh.flags = 0;
> -    encap_nsh.mdtype = NSH_M_TYPE1;
> -    encap_nsh.mdlen = NSH_M_TYPE1_MDLEN;
> -    encap_nsh.path_hdr = htonl(255);
> -    memset(encap_nsh.metadata, 0, NSH_M_TYPE1_MDLEN);
> +    nsh.flags = 0;
> +    nsh.mdtype = NSH_M_TYPE1;
> +    nsh.np = NSH_P_ETHERNET;
> +    nsh.spi = 0;
> +    nsh.si = 255;
> +    memset(nsh.context, 0, NSH_M_TYPE1_MDLEN);
>
>      for (;;) {
>          n += strspn(s + n, delimiters);
> @@ -1808,17 +1869,17 @@ parse_odp_encap_nsh_action(const char *s,
> struct ofpbuf *actions)
>              break;
>          }
>
> -        if (ovs_scan_len(s, &n, "flags=%"SCNi8, &encap_nsh.flags)) {
> +        if (ovs_scan_len(s, &n, "flags=%"SCNi8, &nsh.flags)) {
>              continue;
>          }
> -        if (ovs_scan_len(s, &n, "mdtype=%"SCNi8, &encap_nsh.mdtype)) {
> -            switch (encap_nsh.mdtype) {
> +        if (ovs_scan_len(s, &n, "mdtype=%"SCNi8, &nsh.mdtype)) {
> +            switch (nsh.mdtype) {
>              case NSH_M_TYPE1:
>                  /* This is the default format. */;
>                  break;
>              case NSH_M_TYPE2:
>                  /* Length will be updated later. */
> -                encap_nsh.mdlen = 0;
> +                md_size = 0;
>                  break;
>              default:
>                  ret = -EINVAL;
> @@ -1826,65 +1887,60 @@ parse_odp_encap_nsh_action(const char *s,
> struct ofpbuf *actions)
>              }
>              continue;
>          }
> -        if (ovs_scan_len(s, &n, "np=%"SCNi8, &encap_nsh.np)) {
> +        if (ovs_scan_len(s, &n, "np=%"SCNi8, &nsh.np)) {
>              continue;
>          }
> -        if (ovs_scan_len(s, &n, "spi=0x%"SCNx32, &spi)) {
> -            encap_nsh.path_hdr =
> -                    htonl(((spi << NSH_SPI_SHIFT) & NSH_SPI_MASK) |
> -                            (ntohl(encap_nsh.path_hdr) & ~NSH_SPI_MASK));
> +        if (ovs_scan_len(s, &n, "spi=0x%"SCNx32, &nsh.spi)) {
> +            nsh.spi = htonl(nsh.spi);
>              continue;
>          }
> -        if (ovs_scan_len(s, &n, "si=%"SCNi8, &si)) {
> -            encap_nsh.path_hdr =
> -                    htonl((si << NSH_SI_SHIFT) |
> -                            (ntohl(encap_nsh.path_hdr) & ~NSH_SI_MASK));
> +        if (ovs_scan_len(s, &n, "si=%"SCNi8, &nsh.si)) {
>              continue;
>          }
> -        if (encap_nsh.mdtype == NSH_M_TYPE1) {
> -            struct nsh_md1_ctx *md1 =
> -                ALIGNED_CAST(struct nsh_md1_ctx *, encap_nsh.metadata);
> +        if (nsh.mdtype == NSH_M_TYPE1) {
>              if (ovs_scan_len(s, &n, "c1=0x%"SCNx32, &cd)) {
> -                put_16aligned_be32(&md1->c[0], htonl(cd));
> +                nsh.context[0] = htonl(cd);
>                  continue;
>              }
>              if (ovs_scan_len(s, &n, "c2=0x%"SCNx32, &cd)) {
> -                put_16aligned_be32(&md1->c[1], htonl(cd));
> +                nsh.context[1] = htonl(cd);
>                  continue;
>              }
>              if (ovs_scan_len(s, &n, "c3=0x%"SCNx32, &cd)) {
> -                put_16aligned_be32(&md1->c[2], htonl(cd));
> +                nsh.context[2] = htonl(cd);
>                  continue;
>              }
>              if (ovs_scan_len(s, &n, "c4=0x%"SCNx32, &cd)) {
> -                put_16aligned_be32(&md1->c[3], htonl(cd));
> +                nsh.context[3] = htonl(cd);
>                  continue;
>              }
>          }
> -        else if (encap_nsh.mdtype == NSH_M_TYPE2) {
> +        else if (nsh.mdtype == NSH_M_TYPE2) {
>              struct ofpbuf b;
>              char buf[512];
>              size_t mdlen;
>              if (ovs_scan_len(s, &n, "md2=0x%511[0-9a-fA-F]", buf)) {
> -                ofpbuf_use_stub(&b, encap_nsh.metadata,
> -                                OVS_ENCAP_NSH_MAX_MD_LEN);
> +                metadata = xmalloc(NSH_M_TYPE2_MAX_LEN - 8);
> +                ofpbuf_use_stub(&b, metadata,
> +                                NSH_M_TYPE2_MAX_LEN - 8);
>                  ofpbuf_put_hex(&b, buf, &mdlen);
> -                encap_nsh.mdlen = mdlen;
> +                md_size = mdlen;
>                  ofpbuf_uninit(&b);
>              }
>              continue;
>          }
>      }
>  out:
> -    if (ret < 0) {
> -        return ret;
> -    } else {
> -        size_t size = offsetof(struct ovs_action_encap_nsh, metadata)
> -                + ROUND_UP(encap_nsh.mdlen, 4);
> -        nl_msg_put_unspec(actions, OVS_ACTION_ATTR_ENCAP_NSH,
> -                          &encap_nsh, size);
> -        return n;
> +    if (ret >= 0) {
> +        size_t offset = nl_msg_start_nested(actions,
> OVS_ACTION_ATTR_PUSH_NSH);
> +        nsh_key_to_attr(actions, &nsh, metadata, md_size, false);
> +        nl_msg_end_nested(actions, offset);
> +        ret = n;
>      }
> +    if (metadata != NULL) {
> +        free(metadata);
> +    }
> +    return ret;
>  }
>
>  static int
> @@ -2089,8 +2145,8 @@ parse_odp_action(const char *s, const struct
> simap *port_names,
>      }
>
>      {
> -        if (!strncmp(s, "encap_nsh(", 10)) {
> -            int retval = parse_odp_encap_nsh_action(s, actions);
> +        if (!strncmp(s, "push_nsh(", 9)) {
> +            int retval = parse_odp_push_nsh_action(s, actions);
>              if (retval < 0) {
>                  return retval;
>              }
> @@ -2100,8 +2156,8 @@ parse_odp_action(const char *s, const struct
> simap *port_names,
>
>      {
>          int n;
> -        if (ovs_scan(s, "decap_nsh()%n", &n)) {
> -            nl_msg_put_flag(actions, OVS_ACTION_ATTR_DECAP_NSH);
> +        if (ovs_scan(s, "pop_nsh()%n", &n)) {
> +            nl_msg_put_flag(actions, OVS_ACTION_ATTR_POP_NSH);
>              return n;
>          }
>      }
> @@ -2198,6 +2254,13 @@ static const struct attr_len_tbl
> ovs_tun_key_attr_lens[OVS_TUNNEL_KEY_ATTR_MAX +
>      [OVS_TUNNEL_KEY_ATTR_IPV6_DST]      = { .len = 16 },
>  };
>
> +static const struct attr_len_tbl
> +ovs_nsh_key_attr_lens[OVS_NSH_KEY_ATTR_MAX + 1] = {
> +    [OVS_NSH_KEY_ATTR_BASE]     = { .len = 8 },
> +    [OVS_NSH_KEY_ATTR_MD1]      = { .len = 16 },
> +    [OVS_NSH_KEY_ATTR_MD2]      = { .len = ATTR_LEN_VARIABLE },
> +};
> +
>  static const struct attr_len_tbl
> ovs_flow_key_attr_lens[OVS_KEY_ATTR_MAX + 1] = {
>      [OVS_KEY_ATTR_ENCAP]     = { .len = ATTR_LEN_NESTED },
>      [OVS_KEY_ATTR_PRIORITY]  = { .len = 4 },
> @@ -2229,7 +2292,9 @@ static const struct attr_len_tbl
> ovs_flow_key_attr_lens[OVS_KEY_ATTR_MAX + 1] =
>      [OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4] = { .len = sizeof(struct
> ovs_key_ct_tuple_ipv4) },
>      [OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6] = { .len = sizeof(struct
> ovs_key_ct_tuple_ipv6) },
>      [OVS_KEY_ATTR_PACKET_TYPE] = { .len = 4  },
> -    [OVS_KEY_ATTR_NSH]       = { .len = sizeof(struct ovs_key_nsh) },
> +    [OVS_KEY_ATTR_NSH]       = { .len = ATTR_LEN_NESTED,
> +                                 .next = ovs_nsh_key_attr_lens,
> +                                 .next_max = OVS_NSH_KEY_ATTR_MAX },
>  };
>
>  /* Returns the correct length of the payload for a flow key attribute of the
> @@ -2280,6 +2345,135 @@ ovs_frag_type_to_string(enum ovs_frag_type
> type)
>      }
>  }
>
> +enum odp_key_fitness
> +odp_nsh_hdr_from_attr(const struct nlattr *attr,
> +                      struct nsh_hdr *nsh_hdr)
> +{
> +    unsigned int left;
> +    const struct nlattr *a;
> +    bool unknown = false;
> +    uint8_t flags = 0;
> +    size_t mdlen = 0;
> +    bool has_md1 = false;
> +    bool has_md2 = false;
> +
> +    NL_NESTED_FOR_EACH (a, left, attr) {
> +        uint16_t type = nl_attr_type(a);
> +        size_t len = nl_attr_get_size(a);
> +        int expected_len = odp_key_attr_len(ovs_nsh_key_attr_lens,
> +                                            OVS_NSH_KEY_ATTR_MAX, type);
> +
> +        if (len != expected_len && expected_len >= 0) {
> +            return ODP_FIT_ERROR;
> +        }
> +
> +        switch (type) {
> +        case OVS_NSH_KEY_ATTR_BASE: {
> +            const struct ovs_nsh_key_base *base = nl_attr_get(a);
> +            nsh_hdr->next_proto = base->np;
> +            nsh_hdr->md_type = base->mdtype;
> +            put_16aligned_be32(&nsh_hdr->path_hdr, base->path_hdr);
> +            flags = base->flags;
> +            break;
> +        }
> +        case OVS_NSH_KEY_ATTR_MD1: {
> +            const struct ovs_nsh_key_md1 *md1 = nl_attr_get(a);
> +            struct nsh_md1_ctx *md1_dst = nsh_md1_ctx(nsh_hdr);
> +            has_md1 = true;
> +            mdlen = sizeof *md1;
> +            memcpy(md1_dst, md1, mdlen);
> +            break;
> +        }
> +        case OVS_NSH_KEY_ATTR_MD2: {
> +            struct nsh_md2_tlv *md2_dst = nsh_md2_ctx(nsh_hdr);
> +            const uint8_t *md2 = nl_attr_get(a);
> +            has_md2 = true;
> +            mdlen = nl_attr_get_size(a);
> +            memcpy(md2_dst, md2, mdlen);
> +            break;
> +        }
> +        default:
> +            /* Allow this to show up as unexpected, if there are unknown
> +             * tunnel attribute, eventually resulting in ODP_FIT_TOO_MUCH.
> */
> +            unknown = true;
> +            break;
> +        }
> +    }
> +
> +    if (unknown) {
> +        return ODP_FIT_TOO_MUCH;
> +    }
> +
> +    if ((has_md1 && nsh_hdr->md_type != NSH_M_TYPE1)
> +        || (has_md2 && nsh_hdr->md_type != NSH_M_TYPE2)) {
> +        return ODP_FIT_ERROR;
> +    }
> +
> +    /* nsh header length  = NSH_BASE_HDR_LEN + mdlen */
> +    nsh_hdr->ver_flags_len = htons(flags << NSH_FLAGS_SHIFT |
> +                               (NSH_BASE_HDR_LEN + mdlen) >> 2);
> +
> +    return ODP_FIT_PERFECT;
> +}
> +
> +enum odp_key_fitness
> +odp_nsh_key_from_attr(const struct nlattr *attr, struct flow_nsh *nsh)
> +{
> +    unsigned int left;
> +    const struct nlattr *a;
> +    bool unknown = false;
> +    bool has_md1 = false;
> +
> +    NL_NESTED_FOR_EACH (a, left, attr) {
> +        uint16_t type = nl_attr_type(a);
> +        size_t len = nl_attr_get_size(a);
> +        int expected_len = odp_key_attr_len(ovs_nsh_key_attr_lens,
> +                                            OVS_NSH_KEY_ATTR_MAX, type);
> +
> +        if (len != expected_len && expected_len >= 0) {
> +            return ODP_FIT_ERROR;
> +        }
> +
> +        switch (type) {
> +        case OVS_NSH_KEY_ATTR_BASE: {
> +            const struct ovs_nsh_key_base *base = nl_attr_get(a);
> +            nsh->flags = base->flags;
> +            nsh->mdtype = base->mdtype;
> +            nsh->np = base->np;
> +            nsh->spi = htonl((ntohl(base->path_hdr) & NSH_SPI_MASK) >>
> +                                 NSH_SPI_SHIFT);
> +            nsh->si = (ntohl(base->path_hdr) & NSH_SI_MASK) >>
> NSH_SI_SHIFT;
> +            break;
> +        }
> +        case OVS_NSH_KEY_ATTR_MD1: {
> +            const struct ovs_nsh_key_md1 *md1 = nl_attr_get(a);
> +            has_md1 = true;
> +            nsh->context[0] = md1->context[0];
> +            nsh->context[1] = md1->context[1];
> +            nsh->context[2] = md1->context[2];
> +            nsh->context[3] = md1->context[3];
> +            break;
> +        }
> +        case OVS_NSH_KEY_ATTR_MD2:
> +        default:
> +            /* Allow this to show up as unexpected, if there are unknown
> +             * tunnel attribute, eventually resulting in ODP_FIT_TOO_MUCH.
> */
> +            unknown = true;
> +            break;
> +        }
> +    }
> +
> +    if (unknown) {
> +        return ODP_FIT_TOO_MUCH;
> +    }
> +
> +    if (has_md1 && nsh->mdtype != NSH_M_TYPE1) {
> +        return ODP_FIT_ERROR;
> +    }
> +
> +    return ODP_FIT_PERFECT;
> +}
> +
>  static enum odp_key_fitness
>  odp_tun_key_from_attr__(const struct nlattr *attr, bool is_mask,
>                          struct flow_tnl *tun)
> @@ -2971,6 +3165,80 @@ format_odp_tun_geneve(const struct nlattr
> *attr,
>  }
>
>  static void
> +format_odp_nsh_attr(const struct nlattr *attr, const struct nlattr
> *mask_attr,
> +                    struct ds *ds)
> +{
> +    unsigned int left;
> +    const struct nlattr *a;
> +    struct ovs_key_nsh nsh;
> +    struct ovs_key_nsh nsh_mask;
> +
> +    memset(&nsh, 0, sizeof nsh);
> +    memset(&nsh_mask, 0xff, sizeof nsh_mask);
> +
> +    NL_NESTED_FOR_EACH (a, left, attr) {
> +        enum ovs_nsh_key_attr type = nl_attr_type(a);
> +        const struct nlattr *ma = NULL;
> +
> +        if (mask_attr) {
> +            ma = nl_attr_find__(nl_attr_get(mask_attr),
> +                                nl_attr_get_size(mask_attr), type);
> +        }
> +
> +        if (!check_attr_len(ds, a, ma, ovs_nsh_key_attr_lens,
> +                            OVS_NSH_KEY_ATTR_MAX, true)) {
> +            continue;
> +        }
> +
> +        switch (type) {
> +        case OVS_NSH_KEY_ATTR_BASE: {
> +            const struct ovs_nsh_key_base * base = nl_attr_get(a);
> +            const struct ovs_nsh_key_base * base_mask
> +                = ma ? nl_attr_get(ma) : NULL;
> +            nsh.flags = base->flags;
> +            nsh.mdtype = base->mdtype;
> +            nsh.np = base->np;
> +            nsh.path_hdr = base->path_hdr;
> +            if (base_mask) {
> +                nsh_mask.flags = base_mask->flags;
> +                nsh_mask.mdtype = base_mask->mdtype;
> +                nsh_mask.np = base_mask->np;
> +                nsh_mask.path_hdr = base_mask->path_hdr;
> +            }
> +            break;
> +        }
> +        case OVS_NSH_KEY_ATTR_MD1: {
> +            const struct ovs_nsh_key_md1 * md1 = nl_attr_get(a);
> +            const struct ovs_nsh_key_md1 * md1_mask
> +                = ma ? nl_attr_get(ma) : NULL;
> +            nsh.context[0] = md1->context[0];
> +            nsh.context[1] = md1->context[1];
> +            nsh.context[2] = md1->context[2];
> +            nsh.context[3] = md1->context[3];
> +            if (md1_mask) {
> +                nsh_mask.context[0] = md1_mask->context[0];
> +                nsh_mask.context[1] = md1_mask->context[1];
> +                nsh_mask.context[2] = md1_mask->context[2];
> +                nsh_mask.context[3] = md1_mask->context[3];
> +            }
> +            break;
> +        }
> +        case OVS_NSH_KEY_ATTR_MD2:
> +        case __OVS_NSH_KEY_ATTR_MAX:
> +        default:
> +            /* No support for matching other metadata formats yet. */
> +            break;
> +        }
> +    }
> +
> +    if (mask_attr) {
> +        format_nsh_key_mask(ds, &nsh, &nsh_mask);
> +    } else {
> +        format_nsh_key(ds, &nsh);
> +    }
> +}
> +
> +static void
>  format_odp_tun_attr(const struct nlattr *attr, const struct nlattr
> *mask_attr,
>                      struct ds *ds, bool verbose)
>  {
> @@ -3448,9 +3716,7 @@ format_odp_key_attr__(const struct nlattr *a,
> const struct nlattr *ma,
>          break;
>      }
>      case OVS_KEY_ATTR_NSH: {
> -        const struct ovs_key_nsh *mask = ma ? nl_attr_get(ma) : NULL;
> -        const struct ovs_key_nsh *key = nl_attr_get(a);
> -        format_nsh_key_mask(ds, key, mask);
> +        format_odp_nsh_attr(a, ma, ds);
>          break;
>      }
>      case OVS_KEY_ATTR_UNSPEC:
> @@ -4549,6 +4815,129 @@ geneve_to_attr(struct ofpbuf *a, const void
> *data_)
>      } SCAN_END_SINGLE(ATTR)
>
>  static int
> +parse_odp_nsh_key_mask_attr(const char *s, struct ofpbuf *key,
> +                            struct ofpbuf *mask)
> +{
> +    if (strncmp(s, "nsh(", 4) == 0) {
> +        const char *start = s;
> +        int len;
> +        struct flow_nsh skey, smask;
> +
> +        s += 4;
> +
> +        memset(&skey, 0, sizeof skey);
> +        memset(&smask, 0, sizeof smask);
> +        do {
> +            len = 0;
> +
> +            if (strncmp(s, "flags=", 6) == 0) {
> +                s += 6;
> +                len = scan_u8(s, &skey.flags, mask ? &smask.flags : NULL);
> +                if (len == 0) {
> +                    return -EINVAL;
> +                }
> +                s += len;
> +                continue;
> +            }
> +
> +            if (strncmp(s, "mdtype=", 7) == 0) {
> +                s += 7;
> +                len = scan_u8(s, &skey.mdtype, mask ? &smask.mdtype : NULL);
> +                if (len == 0) {
> +                    return -EINVAL;
> +                }
> +                s += len;
> +                continue;
> +            }
> +
> +            if (strncmp(s, "np=", 3) == 0) {
> +                s += 3;
> +                len = scan_u8(s, &skey.np, mask ? &smask.np : NULL);
> +                if (len == 0) {
> +                    return -EINVAL;
> +                }
> +                s += len;
> +                continue;
> +            }
> +
> +            if (strncmp(s, "spi=", 4) == 0) {
> +                s += 4;
> +                len = scan_be32(s, &skey.spi, mask ? &smask.spi : NULL);
> +                if (len == 0) {
> +                    return -EINVAL;
> +                }
> +                s += len;
> +                continue;
> +            }
> +
> +            if (strncmp(s, "si=", 3) == 0) {
> +                s += 3;
> +                len = scan_u8(s, &skey.si, mask ? &smask.si : NULL);
> +                if (len == 0) {
> +                    return -EINVAL;
> +                }
> +                s += len;
> +                continue;
> +            }
> +
> +            if (strncmp(s, "c1=", 3) == 0) {
> +                s += 3;
> +                len = scan_be32(s, &skey.context[0],
> +                                mask ? &smask.context[0] : NULL);
> +                if (len == 0) {
> +                    return -EINVAL;
> +                }
> +                s += len;
> +                continue;
> +            }
> +
> +            if (strncmp(s, "c2=", 3) == 0) {
> +                s += 3;
> +                len = scan_be32(s, &skey.context[1],
> +                                mask ? &smask.context[1] : NULL);
> +                if (len == 0) {
> +                    return -EINVAL;
> +                }
> +                s += len;
> +                continue;
> +            }
> +
> +            if (strncmp(s, "c3=", 3) == 0) {
> +                s += 3;
> +                len = scan_be32(s, &skey.context[2],
> +                                mask ? &smask.context[2] : NULL);
> +                if (len == 0) {
> +                    return -EINVAL;
> +                }
> +                s += len;
> +                continue;
> +            }
> +
> +            if (strncmp(s, "c4=", 3) == 0) {
> +                s += 3;
> +                len = scan_be32(s, &skey.context[3],
> +                                mask ? &smask.context[3] : NULL);
> +                if (len == 0) {
> +                    return -EINVAL;
> +                }
> +                s += len;
> +                continue;
> +            }
> +        } while (*s++ == ',' && len != 0);
> +        if (s[-1] != ')') {
> +            return -EINVAL;
> +        }
> +
> +        nsh_key_to_attr(key, &skey, NULL, 0, false);
> +        if (mask) {
> +            nsh_key_to_attr(mask, &smask, NULL, 0, true);
> +        }
> +        return s - start;
> +    }
> +    return 0;
> +}
> +
> +static int
>  parse_odp_key_mask_attr(const char *s, const struct simap *port_names,
>                          struct ofpbuf *key, struct ofpbuf *mask)
>  {
> @@ -4694,16 +5083,13 @@ parse_odp_key_mask_attr(const char *s,
> const struct simap *port_names,
>          SCAN_FIELD("id=", be16, id);
>      } SCAN_END(OVS_KEY_ATTR_PACKET_TYPE);
>
> -    SCAN_BEGIN("nsh(", struct ovs_key_nsh) {
> -        SCAN_FIELD("flags=", u8, flags);
> -        SCAN_FIELD("mdtype=", u8, mdtype);
> -        SCAN_FIELD("np=", u8, np);
> -        SCAN_FIELD("path_hdr=", be32, path_hdr);
> -        SCAN_FIELD("c1=", be32, c[0]);
> -        SCAN_FIELD("c2=", be32, c[1]);
> -        SCAN_FIELD("c3=", be32, c[2]);
> -        SCAN_FIELD("c4=", be32, c[2]);
> -    } SCAN_END(OVS_KEY_ATTR_NSH);
> +    /* nsh is nested, it needs special process */
> +    int ret = parse_odp_nsh_key_mask_attr(s, key, mask);
> +    if (ret < 0) {
> +       return ret;
> +    } else {
> +       s += ret;
> +    }
>
>      /* Encap open-coded. */
>      if (!strncmp(s, "encap(", 6)) {
> @@ -4994,11 +5380,7 @@ odp_flow_key_from_flow__(const struct
> odp_flow_key_parms *parms,
>              mpls_key[i].mpls_lse = data->mpls_lse[i];
>          }
>      } else if (flow->dl_type == htons(ETH_TYPE_NSH)) {
> -        struct ovs_key_nsh *nsh_key;
> -
> -        nsh_key = nl_msg_put_unspec_uninit(buf, OVS_KEY_ATTR_NSH,
> -                                            sizeof *nsh_key);
> -        get_nsh_key(data, nsh_key, export_mask);
> +        nsh_key_to_attr(buf, &data->nsh, NULL, 0, export_mask);
>      }
>
>      if (is_ip_any(flow) && !(flow->nw_frag & FLOW_NW_FRAG_LATER)) {
> @@ -5558,13 +5940,10 @@ parse_l2_5_onward(const struct nlattr
> *attrs[OVS_KEY_ATTR_MAX + 1],
>              expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_NSH;
>          }
>          if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_NSH)) {
> -            const struct ovs_key_nsh *nsh_key;
> -
> -            nsh_key = nl_attr_get(attrs[OVS_KEY_ATTR_NSH]);
> -            put_nsh_key(nsh_key, flow, false);
> +            odp_nsh_key_from_attr(attrs[OVS_KEY_ATTR_NSH], &flow->nsh);
>              if (is_mask) {
> -                check_start = nsh_key;
> -                check_len = sizeof *nsh_key;
> +                check_start = nl_attr_get(attrs[OVS_KEY_ATTR_NSH]);
> +                check_len = nl_attr_get_size(attrs[OVS_KEY_ATTR_NSH]);
>                  expected_bit = OVS_KEY_ATTR_NSH;
>              }
>          }
> @@ -6620,13 +6999,13 @@ get_nsh_key(const struct flow *flow, struct
> ovs_key_nsh *nsh, bool is_mask)
>                            flow->nsh.si);
>      if (is_mask) {
>          for (int i = 0; i < 4; i++) {
> -            nsh->c[i] = flow->nsh.c[i];
> +            nsh->context[i] = flow->nsh.context[i];
>          }
>      } else {
>          switch (nsh->mdtype) {
>          case NSH_M_TYPE1:
>              for (int i = 0; i < 4; i++) {
> -                nsh->c[i] = flow->nsh.c[i];
> +                nsh->context[i] = flow->nsh.context[i];
>              }
>              break;
>          case NSH_M_TYPE2:
> @@ -6650,17 +7029,125 @@ put_nsh_key(const struct ovs_key_nsh *nsh,
> struct flow *flow,
>      switch (nsh->mdtype) {
>          case NSH_M_TYPE1:
>              for (int i = 0; i < 4; i++) {
> -                flow->nsh.c[i] = nsh->c[i];
> +                flow->nsh.context[i] = nsh->context[i];
>              }
>              break;
>          case NSH_M_TYPE2:
>          default:
>              /* No match support for other MD formats yet. */
> -            memset(flow->nsh.c, 0, sizeof flow->nsh.c);
> +            memset(flow->nsh.context, 0, sizeof flow->nsh.context);
>              break;
>      }
>  }
>
> +static bool
> +commit_nsh(const struct flow_nsh * flow_nsh, bool use_masked_set,
> +           const struct ovs_key_nsh *key, struct ovs_key_nsh *base,
> +           struct ovs_key_nsh *mask, size_t size,
> +           struct ofpbuf *odp_actions)
> +{
> +    enum ovs_key_attr attr = OVS_KEY_ATTR_NSH;
> +
> +    if (memcmp(key, base, size)  == 0) {
> +        /* Mask bits are set when we have either read or set the
> corresponding
> +         * values.  Masked bits will be exact-matched, no need to set them
> +         * if the value did not actually change. */
> +        return false;
> +    }
> +
> +    bool fully_masked = odp_mask_is_exact(attr, mask, size);
> +
> +    if (use_masked_set && !fully_masked) {
> +        size_t nsh_key_ofs;
> +        struct ovs_nsh_key_base nsh_base;
> +        struct ovs_nsh_key_base nsh_base_mask;
> +        struct ovs_nsh_key_md1 md1;
> +        struct ovs_nsh_key_md1 md1_mask;
> +        size_t offset = nl_msg_start_nested(odp_actions,
> +                                            OVS_ACTION_ATTR_SET_MASKED);
> +
> +        nsh_base.flags = key->flags;
> +        nsh_base.mdtype = key->mdtype;
> +        nsh_base.np = key->np;
> +        nsh_base.path_hdr = key->path_hdr;
> +
> +        nsh_base_mask.flags = mask->flags;
> +        nsh_base_mask.mdtype = mask->mdtype;
> +        nsh_base_mask.np = mask->np;
> +        nsh_base_mask.path_hdr = mask->path_hdr;
> +
> +        /* OVS_KEY_ATTR_NSH keys */
> +        nsh_key_ofs = nl_msg_start_nested(odp_actions,
> OVS_KEY_ATTR_NSH);
> +
> +        char *data = nl_msg_put_unspec_uninit(odp_actions,
> +                                              OVS_NSH_KEY_ATTR_BASE,
> +                                              sizeof(nsh_base));
> +        const char *lkey = (char *)&nsh_base, *lmask = (char
> *)&nsh_base_mask;
> +        size_t lkey_size = sizeof(nsh_base);
> +
> +        while (lkey_size--) {
> +            *data++ = *lkey++ & *lmask++;
> +        }
> +
> +        switch (key->mdtype) {
> +        case NSH_M_TYPE1:
> +            for (int i = 0; i < 4; i++) {
> +                md1.context[i] = key->context[i];
> +                md1_mask.context[i] = mask->context[i];
> +            }
> +            data = nl_msg_put_unspec_uninit(odp_actions,
> +                                            OVS_NSH_KEY_ATTR_MD1,
> +                                            sizeof(md1));
> +            lkey = (char *)&md1;
> +            lmask = (char *)&md1_mask;
> +            lkey_size = sizeof(md1);
> +
> +            while (lkey_size--) {
> +                *data++ = *lkey++ & *lmask++;
> +            }
> +            break;
> +        case NSH_M_TYPE2:
> +        default:
> +            /* No match support for other MD formats yet. */
> +            break;
> +        }
> +
> +        /* OVS_KEY_ATTR_NSH masks */
> +        data = nl_msg_put_unspec_uninit(odp_actions,
> +                                        OVS_NSH_KEY_ATTR_BASE,
> +                                        sizeof(nsh_base_mask));
> +        lmask = (char *)&nsh_base_mask;
> +
> +        memcpy(data, lmask, sizeof(nsh_base_mask));
> +
> +        switch (key->mdtype) {
> +        case NSH_M_TYPE1:
> +            data = nl_msg_put_unspec_uninit(odp_actions,
> +                                            OVS_NSH_KEY_ATTR_MD1,
> +                                            sizeof(md1_mask));
> +            lmask = (char *)&md1_mask;
> +            memcpy(data, lmask, sizeof(md1_mask));
> +            break;
> +        case NSH_M_TYPE2:
> +        default:
> +            /* No match support for other MD formats yet. */
> +            break;
> +        }
> +        nl_msg_end_nested(odp_actions, nsh_key_ofs);
> +
> +        nl_msg_end_nested(odp_actions, offset);
> +    } else {
> +        if (!fully_masked) {
> +            memset(mask, 0xff, size);
> +        }
> +        size_t offset = nl_msg_start_nested(odp_actions,
> OVS_ACTION_ATTR_SET);
> +        nsh_key_to_attr(odp_actions, flow_nsh, NULL, 0, false);
> +        nl_msg_end_nested(odp_actions, offset);
> +    }
> +    memcpy(base, key, size);
> +    return true;
> +}
> +
>  static void
>  commit_set_nsh_action(const struct flow *flow, struct flow *base_flow,
>                        struct ofpbuf *odp_actions,
> @@ -6684,8 +7171,8 @@ commit_set_nsh_action(const struct flow *flow,
> struct flow *base_flow,
>      mask.mdtype = 0;     /* Not writable. */
>      mask.np = 0;         /* Not writable. */
>
> -    if (commit(OVS_KEY_ATTR_NSH, use_masked, &key, &base, &mask,
> sizeof key,
> -               odp_actions)) {
> +    if (commit_nsh(&base_flow->nsh, use_masked, &key, &base, &mask,
> +            sizeof key, odp_actions)) {
>          put_nsh_key(&base, base_flow, false);
>          if (mask.mdtype != 0) { /* Mask was changed by commit(). */
>              put_nsh_key(&mask, &wc->masks, true);
> @@ -6788,49 +7275,36 @@ commit_set_pkt_mark_action(const struct
> flow *flow, struct flow *base_flow,
>  }
>
>  static void
> -odp_put_decap_nsh_action(struct ofpbuf *odp_actions)
> +odp_put_pop_nsh_action(struct ofpbuf *odp_actions)
>  {
> -    nl_msg_put_flag(odp_actions, OVS_ACTION_ATTR_DECAP_NSH);
> +    nl_msg_put_flag(odp_actions, OVS_ACTION_ATTR_POP_NSH);
>  }
>
>  static void
> -odp_put_encap_nsh_action(struct ofpbuf *odp_actions,
> +odp_put_push_nsh_action(struct ofpbuf *odp_actions,
>                           const struct flow *flow,
>                           struct ofpbuf *encap_data)
>  {
> -    struct ovs_action_encap_nsh encap_nsh;
> -
> -    encap_nsh.flags = flow->nsh.flags;
> -    encap_nsh.mdtype = flow->nsh.mdtype;
> -    encap_nsh.np = flow->nsh.np;
> -    encap_nsh.path_hdr = htonl((ntohl(flow->nsh.spi) << NSH_SPI_SHIFT) |
> -                                   flow->nsh.si);
> +    uint8_t * metadata = NULL;
> +    uint8_t md_size = 0;
>
> -    switch (encap_nsh.mdtype) {
> -    case NSH_M_TYPE1: {
> -        struct nsh_md1_ctx *md1 =
> -            ALIGNED_CAST(struct nsh_md1_ctx *, encap_nsh.metadata);
> -        encap_nsh.mdlen = NSH_M_TYPE1_MDLEN;
> -        for (int i = 0; i < 4; i++) {
> -            put_16aligned_be32(&md1->c[i], flow->nsh.c[i]);
> -        }
> -        break;
> -    }
> +    switch (flow->nsh.mdtype) {
>      case NSH_M_TYPE2:
>          if (encap_data) {
> -            ovs_assert(encap_data->size < OVS_ENCAP_NSH_MAX_MD_LEN);
> -            encap_nsh.mdlen = encap_data->size;
> -            memcpy(encap_nsh.metadata, encap_data->data, encap_data-
> >size);
> +            ovs_assert(encap_data->size < OVS_PUSH_NSH_MAX_MD_LEN);
> +            metadata = encap_data->data;
> +            md_size = encap_data->size;
>          } else {
> -            encap_nsh.mdlen = 0;
> +            md_size = 0;
>          }
>          break;
>      default:
> -        encap_nsh.mdlen = 0;
> +        md_size = 0;
>          break;
>      }
> -    nl_msg_put_unspec(odp_actions, OVS_ACTION_ATTR_ENCAP_NSH,
> -                      &encap_nsh, sizeof(encap_nsh));
> +    size_t offset = nl_msg_start_nested(odp_actions,
> OVS_ACTION_ATTR_PUSH_NSH);
> +    nsh_key_to_attr(odp_actions, &flow->nsh, metadata, md_size, false);
> +    nl_msg_end_nested(odp_actions, offset);
>  }
>
>  static void
> @@ -6857,8 +7331,8 @@ commit_packet_type_change(const struct flow
> *flow,
>              break;
>          }
>          case PT_NSH:
> -            /* encap_nsh */
> -            odp_put_encap_nsh_action(odp_actions, flow, encap_data);
> +            /* push_nsh */
> +            odp_put_push_nsh_action(odp_actions, flow, encap_data);
>              base_flow->packet_type = flow->packet_type;
>              /* Update all packet headers in base_flow. */
>              memcpy(&base_flow->dl_dst, &flow->dl_dst,
> @@ -6883,8 +7357,8 @@ commit_packet_type_change(const struct flow
> *flow,
>               * No need to update the base flow here. */
>              switch (ntohl(base_flow->packet_type)) {
>              case PT_NSH:
> -                /* decap_nsh. */
> -                odp_put_decap_nsh_action(odp_actions);
> +                /* pop_nsh. */
> +                odp_put_pop_nsh_action(odp_actions);
>                  break;
>              default:
>                  /* Checks are done during translation. */
> diff --git a/lib/odp-util.h b/lib/odp-util.h
> index 27c2ab4..bd6be60 100644
> --- a/lib/odp-util.h
> +++ b/lib/odp-util.h
> @@ -158,6 +158,10 @@ struct odputil_keybuf {
>
>  enum odp_key_fitness odp_tun_key_from_attr(const struct nlattr *,
>                                             struct flow_tnl *);
> +enum odp_key_fitness odp_nsh_key_from_attr(const struct nlattr *,
> +                                           struct flow_nsh *);
> +enum odp_key_fitness odp_nsh_hdr_from_attr(const struct nlattr *,
> +                                           struct nsh_hdr *);
>
>  int odp_ufid_from_string(const char *s_, ovs_u128 *ufid);
>  void odp_format_ufid(const ovs_u128 *ufid, struct ds *);
> diff --git a/lib/packets.c b/lib/packets.c
> index 74d87ed..db98ab3 100644
> --- a/lib/packets.c
> +++ b/lib/packets.c
> @@ -403,10 +403,10 @@ pop_mpls(struct dp_packet *packet, ovs_be16
> ethtype)
>  }
>
>  void
> -encap_nsh(struct dp_packet *packet, const struct ovs_action_encap_nsh
> *encap)
> +push_nsh(struct dp_packet *packet, const struct nsh_hdr *nsh_hdr_src)
>  {
>      struct nsh_hdr *nsh;
> -    size_t length = NSH_BASE_HDR_LEN + encap->mdlen;
> +    size_t length = nsh_hdr_len(nsh_hdr_src);
>      uint8_t next_proto;
>
>      switch (ntohl(packet->packet_type)) {
> @@ -427,23 +427,8 @@ encap_nsh(struct dp_packet *packet, const struct
> ovs_action_encap_nsh *encap)
>      }
>
>      nsh = (struct nsh_hdr *) dp_packet_push_uninit(packet, length);
> -    nsh->ver_flags_len = htons(encap->flags << NSH_FLAGS_SHIFT | length
> >> 2);
> +    memcpy(nsh, nsh_hdr_src, length);
>      nsh->next_proto = next_proto;
> -    put_16aligned_be32(&nsh->path_hdr, encap->path_hdr);
> -    nsh->md_type = encap->mdtype;
> -    switch (nsh->md_type) {
> -        case NSH_M_TYPE1:
> -            nsh->md1 = *ALIGNED_CAST(struct nsh_md1_ctx *, encap-
> >metadata);
> -            break;
> -        case NSH_M_TYPE2: {
> -            /* The MD2 metadata in encap is already padded to 4 bytes. */
> -            size_t len = ROUND_UP(encap->mdlen, 4);
> -            memcpy(&nsh->md2, encap->metadata, len);
> -            break;
> -        }
> -        default:
> -            OVS_NOT_REACHED();
> -    }
>
>      packet->packet_type = htonl(PT_NSH);
>      dp_packet_reset_offsets(packet);
> @@ -451,7 +436,7 @@ encap_nsh(struct dp_packet *packet, const struct
> ovs_action_encap_nsh *encap)
>  }
>
>  bool
> -decap_nsh(struct dp_packet *packet)
> +pop_nsh(struct dp_packet *packet)
>  {
>      struct nsh_hdr *nsh = (struct nsh_hdr *) dp_packet_l3(packet);
>      size_t length;
> diff --git a/lib/packets.h b/lib/packets.h
> index 705d0b2..e7832ba 100644
> --- a/lib/packets.h
> +++ b/lib/packets.h
> @@ -434,9 +434,8 @@ void push_eth(struct dp_packet *packet, const
> struct eth_addr *dst,
>                const struct eth_addr *src);
>  void pop_eth(struct dp_packet *packet);
>
> -void encap_nsh(struct dp_packet *packet,
> -               const struct ovs_action_encap_nsh *encap_nsh);
> -bool decap_nsh(struct dp_packet *packet);
> +void push_nsh(struct dp_packet *packet, const struct nsh_hdr
> *nsh_hdr_src);
> +bool pop_nsh(struct dp_packet *packet);
>
>  #define LLC_DSAP_SNAP 0xaa
>  #define LLC_SSAP_SNAP 0xaa
> diff --git a/ofproto/ofproto-dpif-ipfix.c b/ofproto/ofproto-dpif-ipfix.c
> index 472c272..16976c4 100644
> --- a/ofproto/ofproto-dpif-ipfix.c
> +++ b/ofproto/ofproto-dpif-ipfix.c
> @@ -2823,8 +2823,8 @@ dpif_ipfix_read_actions(const struct flow *flow,
>          case OVS_ACTION_ATTR_POP_MPLS:
>          case OVS_ACTION_ATTR_PUSH_ETH:
>          case OVS_ACTION_ATTR_POP_ETH:
> -        case OVS_ACTION_ATTR_ENCAP_NSH:
> -        case OVS_ACTION_ATTR_DECAP_NSH:
> +        case OVS_ACTION_ATTR_PUSH_NSH:
> +        case OVS_ACTION_ATTR_POP_NSH:
>          case OVS_ACTION_ATTR_UNSPEC:
>          case __OVS_ACTION_ATTR_MAX:
>          default:
> diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
> index 65a2003..1af1569 100644
> --- a/ofproto/ofproto-dpif-sflow.c
> +++ b/ofproto/ofproto-dpif-sflow.c
> @@ -1199,8 +1199,8 @@ dpif_sflow_read_actions(const struct flow *flow,
>           break;
>       case OVS_ACTION_ATTR_SAMPLE:
>       case OVS_ACTION_ATTR_CLONE:
> -        case OVS_ACTION_ATTR_ENCAP_NSH:
> -        case OVS_ACTION_ATTR_DECAP_NSH:
> +        case OVS_ACTION_ATTR_PUSH_NSH:
> +        case OVS_ACTION_ATTR_POP_NSH:
>       case OVS_ACTION_ATTR_UNSPEC:
>       case __OVS_ACTION_ATTR_MAX:
>       default:
> diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
> index 973e760..437c519 100644
> --- a/ofproto/ofproto-dpif-xlate.c
> +++ b/ofproto/ofproto-dpif-xlate.c
> @@ -4392,8 +4392,8 @@ xlate_fixup_actions(struct ofpbuf *b, const struct
> nlattr *actions,
>          case OVS_ACTION_ATTR_CT:
>          case OVS_ACTION_ATTR_PUSH_ETH:
>          case OVS_ACTION_ATTR_POP_ETH:
> -        case OVS_ACTION_ATTR_ENCAP_NSH:
> -        case OVS_ACTION_ATTR_DECAP_NSH:
> +        case OVS_ACTION_ATTR_PUSH_NSH:
> +        case OVS_ACTION_ATTR_POP_NSH:
>          case OVS_ACTION_ATTR_METER:
>              ofpbuf_put(b, a, nl_attr_len_pad(a, left));
>              break;
> @@ -5804,17 +5804,17 @@ rewrite_flow_encap_ethernet(struct xlate_ctx
> *ctx,
>
>  /* For an MD2 NSH header returns a pointer to an ofpbuf with the
> encoded
>   * MD2 TLVs provided as encap properties to the encap operation. This
> - * will be stored as encap_data in the ctx and copied into the encap_nsh
> + * will be stored as encap_data in the ctx and copied into the push_nsh
>   * action at the next commit. */
>  static struct ofpbuf *
> -rewrite_flow_encap_nsh(struct xlate_ctx *ctx,
> -                       const struct ofpact_encap *encap,
> -                       struct flow *flow,
> -                       struct flow_wildcards *wc)
> +rewrite_flow_push_nsh(struct xlate_ctx *ctx,
> +                      const struct ofpact_encap *encap,
> +                      struct flow *flow,
> +                      struct flow_wildcards *wc)
>  {
>      ovs_be32 packet_type = flow->packet_type;
>      const char *ptr = (char *) encap->props;
> -    struct ofpbuf *buf = ofpbuf_new(OVS_ENCAP_NSH_MAX_MD_LEN);
> +    struct ofpbuf *buf = ofpbuf_new(OVS_PUSH_NSH_MAX_MD_LEN);
>      uint8_t md_type = NSH_M_TYPE1;
>      uint8_t np = 0;
>      int i;
> @@ -5854,7 +5854,7 @@ rewrite_flow_encap_nsh(struct xlate_ctx *ctx,
>          }
>          ptr += ROUND_UP(prop_ptr->len, 8);
>      }
> -    if (buf->size == 0 || buf->size > OVS_ENCAP_NSH_MAX_MD_LEN) {
> +    if (buf->size == 0 || buf->size > OVS_PUSH_NSH_MAX_MD_LEN) {
>          ofpbuf_delete(buf);
>          buf = NULL;
>      }
> @@ -5899,7 +5899,7 @@ rewrite_flow_encap_nsh(struct xlate_ctx *ctx,
>
>      if (md_type == NSH_M_TYPE1) {
>          flow->nsh.mdtype = NSH_M_TYPE1;
> -        memset(flow->nsh.c, 0, sizeof flow->nsh.c);
> +        memset(flow->nsh.context, 0, sizeof flow->nsh.context);
>          if (buf) {
>              /* Drop any MD2 context TLVs. */
>              ofpbuf_delete(buf);
> @@ -5930,7 +5930,7 @@ xlate_generic_encap_action(struct xlate_ctx
> *ctx,
>              rewrite_flow_encap_ethernet(ctx, flow, wc);
>              break;
>          case PT_NSH:
> -            encap_data = rewrite_flow_encap_nsh(ctx, encap, flow, wc);
> +            encap_data = rewrite_flow_push_nsh(ctx, encap, flow, wc);
>              break;
>          default:
>              /* New packet type was checked during decoding. */
> @@ -5973,7 +5973,7 @@ xlate_generic_decap_action(struct xlate_ctx
> *ctx,
>              }
>              return false;
>          case PT_NSH:
> -            /* The decap_nsh action is generated at the commit executed as
> +            /* The pop_nsh action is generated at the commit executed as
>               * part of freezing the ctx for recirculation. Here we just set
>               * the new packet type based on the NSH next protocol field. */
>              switch (flow->nsh.np) {
> diff --git a/tests/nsh.at b/tests/nsh.at
> index aa80a2a..562f3da 100644
> --- a/tests/nsh.at
> +++ b/tests/nsh.at
> @@ -105,7 +105,7 @@ bridge("br0")
>
>  Final flow:
> in_port=1,vlan_tci=0x0000,dl_src=00:00:00:00:00:00,dl_dst=11:22:33:44:55:
> 66,dl_type=0x894f,nsh_flags=0,nsh_mdtype=1,nsh_np=3,nsh_spi=0x1234,ns
> h_si=255,nsh_c1=0x11223344,nsh_c2=0x0,nsh_c3=0x0,nsh_c4=0x0,nw_pro
> to=0,nw_tos=0,nw_ecn=0,nw_ttl=0
>  Megaflow:
> recirc_id=0,eth,ip,in_port=1,dl_dst=66:77:88:99:aa:bb,nw_frag=no
> -Datapath actions:
> encap_nsh(flags=0,mdtype=1,np=3,spi=0x1234,si=255,c1=0x11223344,c2=0
> x0,c3=0x0,c4=0x0),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),
> pop_eth,decap_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x1)
> +Datapath actions:
> push_nsh(flags=0,mdtype=1,np=3,spi=0x1234,si=255,c1=0x11223344,c2=0x
> 0,c3=0x0,c4=0x0),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),p
> op_eth,pop_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x1)
>  ])
>
>  AT_CHECK([
> @@ -121,7 +121,7 @@ bridge("br0")
>
>  Final flow: unchanged
>  Megaflow:
> recirc_id=0,eth,in_port=4,dl_type=0x894f,nsh_mdtype=1,nsh_np=3,nsh_spi
> =0x1234,nsh_c1=0x11223344
> -Datapath actions: pop_eth,decap_nsh(),recirc(0x2)
> +Datapath actions: pop_eth,pop_nsh(),recirc(0x2)
>  ])
>
>  # Now send two real ICMP echo request packets in on port p1
> @@ -139,7 +139,7 @@ ovs-appctl time/warp 1000
>  AT_CHECK([
>      ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v
> ipv6 | sort
>  ], [0], [flow-dump from non-dpdk interfaces:
> -
> recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(dst=1e:2c:e9:2a:66:9e),e
> th_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s,
> actions:encap_nsh(flags=0,mdtype=1,np=3,spi=0x1234,si=255,c1=0x112233
> 44,c2=0x0,c3=0x0,c4=0x0),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44
> :55:66),pop_eth,decap_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x3)
> +recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(dst=1e:2c:e9:2a:66:9e),
> eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s,
> actions:push_nsh(flags=0,mdtype=1,np=3,spi=0x1234,si=255,c1=0x1122334
> 4,c2=0x0,c3=0x0,c4=0x0),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:
> 55:66),pop_eth,pop_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x3)
>
> recirc_id(0x3),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(fra
> g=no), packets:1, bytes:98, used:0.0s, actions:2
>  ])
>
> @@ -170,7 +170,7 @@ ovs-appctl time/warp 1000
>  AT_CHECK([
>      ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v
> ipv6 | sort
>  ], [0], [flow-dump from non-dpdk interfaces:
> -
> recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=
> no), packets:1, bytes:98, used:0.0s,
> actions:push_vlan(vid=100,pcp=0),encap_nsh(flags=0,mdtype=1,np=3,spi=0
> x0,si=255,c1=0x0,c2=0x0,c3=0x0,c4=0x0),decap_nsh(),recirc(0x4)
> +recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag
> =no), packets:1, bytes:98, used:0.0s,
> actions:push_vlan(vid=100,pcp=0),push_nsh(flags=0,mdtype=1,np=3,spi=0x
> 0,si=255,c1=0x0,c2=0x0,c3=0x0,c4=0x0),pop_nsh(),recirc(0x4)
>
> recirc_id(0x4),in_port(1),packet_type(ns=0,id=0),eth_type(0x8100),vlan(vid
> =100,pcp=0),encap(eth_type(0x0800),ipv4(frag=no)), packets:1, bytes:102,
> used:0.0s, actions:2
>  ])
>
> @@ -195,7 +195,7 @@ ovs-vsctl set bridge br0 datapath_type=dummy \
>          add-port br0 v4 -- set Interface v4 type=patch options:peer=v3
> ofport_request=4])
>
>  AT_DATA([flows.txt], [dnl
> -
> table=0,in_port=1,ip,actions=encap(nsh(md_type=2,tlv(0x1000,10,0x12345
> 678))),set_field:0x1234-
> >nsh_spi,encap(ethernet),set_field:11:22:33:44:55:66->dl_dst,3
> +
> table=0,in_port=1,ip,actions=encap(nsh(md_type=2,tlv(0x1000,10,0x12345
> 678),tlv(0x2000,20,0xfedcba9876543210))),set_field:0x1234-
> >nsh_spi,encap(ethernet),set_field:11:22:33:44:55:66->dl_dst,3
>
> table=0,in_port=4,dl_type=0x894f,nsh_mdtype=2,nsh_spi=0x1234,actions=
> decap(),decap(),2
>  ])
>
> @@ -205,7 +205,7 @@ AT_CHECK([
>      ovs-ofctl -Oopenflow13 dump-flows br0 | ofctl_strip | sort | grep
> actions
>  ], [0], [dnl
>   in_port=4,dl_type=0x894f,nsh_mdtype=2,nsh_spi=0x1234
> actions=decap(),decap(),output:2
> - ip,in_port=1
> actions=encap(nsh(md_type=2,tlv(0x1000,10,0x12345678))),set_field:0x123
> 4->nsh_spi,encap(ethernet),set_field:11:22:33:44:55:66->eth_dst,output:3
> + ip,in_port=1
> actions=encap(nsh(md_type=2,tlv(0x1000,10,0x12345678),tlv(0x2000,20,0x
> fedcba9876543210))),set_field:0x1234-
> >nsh_spi,encap(ethernet),set_field:11:22:33:44:55:66->eth_dst,output:3
>  ])
>
>  AT_CHECK([
> @@ -216,7 +216,7 @@ Flow:
> icmp,in_port=1,vlan_tci=0x0000,dl_src=00:11:22:33:44:55,dl_dst=66:77:88:
> 99
>  bridge("br0")
>  -------------
>   0. ip,in_port=1, priority 32768
> -    encap(nsh(md_type=2,tlv(0x1000,10,0x12345678)))
> +
> encap(nsh(md_type=2,tlv(0x1000,10,0x12345678),tlv(0x2000,20,0xfedcba9
> 876543210)))
>      set_field:0x1234->nsh_spi
>      encap(ethernet)
>      set_field:11:22:33:44:55:66->eth_dst
> @@ -230,7 +230,7 @@ bridge("br0")
>
>  Final flow:
> in_port=1,vlan_tci=0x0000,dl_src=00:00:00:00:00:00,dl_dst=11:22:33:44:55:
> 66,dl_type=0x894f,nsh_flags=0,nsh_mdtype=2,nsh_np=3,nsh_spi=0x1234,ns
> h_si=255,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0
>  Megaflow:
> recirc_id=0,eth,ip,in_port=1,dl_dst=66:77:88:99:aa:bb,nw_frag=no
> -Datapath actions:
> encap_nsh(flags=0,mdtype=2,np=3,spi=0x1234,si=255,md2=0x10000a0412
> 345678),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),pop_eth,d
> ecap_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x1)
> +Datapath actions:
> push_nsh(flags=0,mdtype=2,np=3,spi=0x1234,si=255,md2=0x10000a04123
> 4567820001408fedcba9876543210),push_eth(src=00:00:00:00:00:00,dst=11
> :22:33:44:55:66),pop_eth,pop_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(
> 0x1)
>  ])
>
>  AT_CHECK([
> @@ -246,7 +246,7 @@ bridge("br0")
>
>  Final flow: unchanged
>  Megaflow:
> recirc_id=0,eth,in_port=4,dl_type=0x894f,nsh_mdtype=2,nsh_np=3,nsh_spi
> =0x1234
> -Datapath actions: pop_eth,decap_nsh(),recirc(0x2)
> +Datapath actions: pop_eth,pop_nsh(),recirc(0x2)
>  ])
>
>  # Now send two real ICMP echo request packets in on port p1
> @@ -264,7 +264,7 @@ ovs-appctl time/warp 1000
>  AT_CHECK([
>      ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v
> ipv6 | sort
>  ], [0], [flow-dump from non-dpdk interfaces:
> -
> recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(dst=1e:2c:e9:2a:66:9e),e
> th_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s,
> actions:encap_nsh(flags=0,mdtype=2,np=3,spi=0x1234,si=255,md2=0x1000
> 0a0412345678),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),po
> p_eth,decap_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x3)
> +recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(dst=1e:2c:e9:2a:66:9e),
> eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s,
> actions:push_nsh(flags=0,mdtype=2,np=3,spi=0x1234,si=255,md2=0x10000
> a041234567820001408fedcba9876543210),push_eth(src=00:00:00:00:00:0
> 0,dst=11:22:33:44:55:66),pop_eth,pop_nsh(),set(eth(dst=11:22:33:44:55:66)
> ),recirc(0x3)
>
> recirc_id(0x3),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(fra
> g=no), packets:1, bytes:98, used:0.0s, actions:2
>  ])
>
> @@ -577,8 +577,8 @@ ovs-appctl time/warp 1000
>  AT_CHECK([
>      ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v
> ipv6 | sort
>  ], [0], [flow-dump from non-dpdk interfaces:
> -
> recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=1
> 92.168.10.30,frag=no), packets:1, bytes:98, used:0.0s,
> actions:pop_eth,encap_nsh(flags=0,mdtype=1,np=1,spi=0x3000,si=255,c1=0
> x0,c2=0x0,c3=0x0,c4=0x0),clone(tnl_push(tnl_port(4789),header(size=50,ty
> pe=4,eth(dst=aa:55:00:00:00:03,src=aa:55:00:00:00:01,dl_type=0x0800),ipv
> 4(src=10.0.0.1,dst=10.0.0.3,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,d
> st=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(1)),set(ipv4(s
> rc=30.0.0.1,dst=30.0.0.3)),tnl_pop(4789))
> -tunnel(tun_id=0x0,src=30.0.0.1,dst=30.0.0.3,flags(-df-
> csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(np
> =1,spi=0x3000,si=255), packets:1, bytes:108, used:0.0s,
> actions:decap_nsh(),recirc(0x1)
> +recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=
> 192.168.10.30,frag=no), packets:1, bytes:98, used:0.0s,
> actions:pop_eth,push_nsh(flags=0,mdtype=1,np=1,spi=0x3000,si=255,c1=0x
> 0,c2=0x0,c3=0x0,c4=0x0),clone(tnl_push(tnl_port(4789),header(size=50,typ
> e=4,eth(dst=aa:55:00:00:00:03,src=aa:55:00:00:00:01,dl_type=0x0800),ipv4
> (src=10.0.0.1,dst=10.0.0.3,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,ds
> t=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(1)),set(ipv4(sr
> c=30.0.0.1,dst=30.0.0.3)),tnl_pop(4789))
> +tunnel(tun_id=0x0,src=30.0.0.1,dst=30.0.0.3,flags(-df-
> csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(np
> =1,spi=0x3000,si=255), packets:1, bytes:108, used:0.0s,
> actions:pop_nsh(),recirc(0x1)
>  tunnel(tun_id=0x0,src=30.0.0.1,dst=30.0.0.3,flags(-df-
> csum+key)),recirc_id(0x1),in_port(4789),packet_type(ns=1,id=0x800),ipv4(f
> rag=no), packets:1, bytes:84, used:0.0s,
> actions:push_eth(src=00:00:00:00:00:00,dst=aa:55:aa:55:00:03),6
>  ])
>
> @@ -631,9 +631,9 @@ ovs-appctl time/warp 1000
>  AT_CHECK([
>      ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v
> ipv6 | sort
>  ], [0], [flow-dump from non-dpdk interfaces:
> -
> recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=1
> 92.168.10.20/255.255.255.248,frag=no), packets:1, bytes:98, used:0.0s,
> actions:pop_eth,encap_nsh(flags=0,mdtype=1,np=1,spi=0x3020,si=255,c1=0
> x0,c2=0x0,c3=0x0,c4=0x0),clone(tnl_push(tnl_port(4789),header(size=50,ty
> pe=4,eth(dst=aa:55:00:00:00:02,src=aa:55:00:00:00:01,dl_type=0x0800),ipv
> 4(src=10.0.0.1,dst=10.0.0.2,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,d
> st=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(1)),set(ipv4(s
> rc=20.0.0.1,dst=20.0.0.2)),tnl_pop(4789))
> +recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=
> 192.168.10.20/255.255.255.248,frag=no), packets:1, bytes:98, used:0.0s,
> actions:pop_eth,push_nsh(flags=0,mdtype=1,np=1,spi=0x3020,si=255,c1=0x
> 0,c2=0x0,c3=0x0,c4=0x0),clone(tnl_push(tnl_port(4789),header(size=50,typ
> e=4,eth(dst=aa:55:00:00:00:02,src=aa:55:00:00:00:01,dl_type=0x0800),ipv4
> (src=10.0.0.1,dst=10.0.0.2,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,ds
> t=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(1)),set(ipv4(sr
> c=20.0.0.1,dst=20.0.0.2)),tnl_pop(4789))
>  tunnel(tun_id=0x0,src=20.0.0.1,dst=20.0.0.2,flags(-df-
> csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(spi
> =0x3020,si=255), packets:1, bytes:108, used:0.0s,
> actions:push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),set(nsh(spi=
> 0x3020,si=254)),pop_eth,clone(tnl_push(tnl_port(4789),header(size=50,typ
> e=4,eth(dst=aa:55:00:00:00:03,src=aa:55:00:00:00:02,dl_type=0x0800),ipv4
> (src=20.0.0.2,dst=20.0.0.3,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,ds
> t=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(2)),set(ipv4(sr
> c=30.0.0.2,dst=30.0.0.3)),tnl_pop(4789))
> -tunnel(tun_id=0x0,src=30.0.0.2,dst=30.0.0.3,flags(-df-
> csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(np
> =1,spi=0x3020,si=254), packets:1, bytes:108, used:0.0s,
> actions:decap_nsh(),recirc(0x2)
> +tunnel(tun_id=0x0,src=30.0.0.2,dst=30.0.0.3,flags(-df-
> csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(np
> =1,spi=0x3020,si=254), packets:1, bytes:108, used:0.0s,
> actions:pop_nsh(),recirc(0x2)
>  tunnel(tun_id=0x0,src=30.0.0.2,dst=30.0.0.3,flags(-df-
> csum+key)),recirc_id(0x2),in_port(4789),packet_type(ns=1,id=0x800),ipv4(f
> rag=no), packets:1, bytes:84, used:0.0s,
> actions:push_eth(src=00:00:00:00:00:00,dst=aa:55:aa:55:00:03),6
>  ])
>
> --
> 2.1.0
Yang, Yi Aug. 19, 2017, 2 a.m. UTC | #2
On Fri, Aug 18, 2017 at 11:02:35PM +0800, Jan Scheurich wrote:
> > diff --git a/datapath/linux/compat/include/linux/openvswitch.h
> > b/datapath/linux/compat/include/linux/openvswitch.h
> > index bc6c94b..d7f9029 100644
> > --- a/datapath/linux/compat/include/linux/openvswitch.h
> > +++ b/datapath/linux/compat/include/linux/openvswitch.h
>  
> > +#define NSH_MD1_CONTEXT_SIZE 4
> [Jan] This shouldn’t be needed here and replaced by a macro defined in nsh.h

Jan, this is indeed needed here, context[4] won't work because ovs
script will convert array[4] in

datapath/linux/compat/include/linux/openvswitch.h

To a special type, so we have to hack this. In addition,
datapath/linux/compat/include/linux/openvswitch.h shouldn't include
nsh.h anyway.

>  
> > +#define OVS_PUSH_NSH_MAX_MD_LEN 248
> [Jan] This shouldn’t be needed here and replaced by a macro defined in nsh.h
>

ok, I'll remove it from here.
  
> > diff --git a/include/openvswitch/nsh.h b/include/openvswitch/nsh.h
> > index f4ccadc..ed9054e 100644
> > --- a/include/openvswitch/nsh.h
> > +++ b/include/openvswitch/nsh.h
>  
>  
> >  struct nsh_md2_tlv {
> > @@ -72,6 +72,8 @@ struct nsh_hdr {
> >      };
> >  };
> >
> > +#define NSH_M_TYPE2_MAX_LEN 256
> [Jan] This is a duplicate definition (see below)
>

Noted, will remove it.
  
>  
> > +/* NSH MD Type 2 header maximum Length. */
> > +#define NSH_M_TYPE2_MAX_LEN 256
> [Jan] I suggest to rename this to NSH_HEADER_MAX_LEN as it is generic limit for
> any MD type, not only MD2, resulting from the fact that the length in 4 byte
> words is encoded in 6 bits.
>  
> Add a macro NSH_CONTEXT_HDRS_MAX_LEN 248 and use that instead of
> OVS_PUSH_NSH_MAX_MD_LEN.
>  

Ok, will use macros you suggest to replace them.

> >  static inline uint16_t
> >  nsh_hdr_len(const struct nsh_hdr *nsh)
> >  {
> > -    return 4 * (ntohs(nsh->ver_flags_len) & NSH_LEN_MASK) >>
> > NSH_LEN_SHIFT;
> > +    return ((ntohs(nsh->ver_flags_len) & NSH_LEN_MASK) >>
> > NSH_LEN_SHIFT) << 2;
> [Jan] I personally find 4 * X clearer than X << 2 when the purpose really is to
> multiply by 4 rather than to shift left by two bits. The compiler should
> generate similar code with -O2 anyhow.
>

The issue is it is incorrect, Eric Garver told me operator ">>" will be
done before operator "*", it can run correctly just because
NSH_LEN_SHIFT is zero, so the new one is right.

"* 4" and "<< 2" have same readability, we mustn't argue such issue
here.

> > diff --git a/include/openvswitch/packets.h b/include/openvswitch/packets.h
> > index be91e02..5ee3099 100644
> > --- a/include/openvswitch/packets.h
> > +++ b/include/openvswitch/packets.h
> > @@ -84,7 +84,16 @@ struct flow_nsh {
> >      uint8_t np;
> >      uint8_t si;
> >      ovs_be32 spi;
> > -    ovs_be32 c[4];
> > +    ovs_be32 context[4];
> > +};
> > +
> > +struct ovs_key_nsh {
> > +    uint8_t flags;
> > +    uint8_t mdtype;
> > +    uint8_t np;
> > +    uint8_t pad;
> > +    ovs_be32 path_hdr;
> > +    ovs_be32 context[4];
> >  };
> 
> [Jan] I see in the second patch that you use this new struct as replacement for
> struct flow_nsh in struct flow. The main difference being that the new struct
> combines spi and si fields into the path_hdr. That is of course possible
> provided that the necessary conversions are made where needed, but I doubt that
> it improves readability in the code overall.
>  
> Was the main motivation to use the same data type both in struct flow as well
> as in the datapath for set(nsh) operations? Or did you want to prevent struct
> flow_nsh spilling over into 25 bytes as the spare octet used for the si field
> is now needed for the new ttl field?
>  
> I believe in the datapath the NSH key attributes could be parsed into an NSH
> header struct directly, no need to have an struct ovs_key_nsh.

Jan, struct flow_nsh must be 64 bits aligned, new key ttl broke this.
struct ovs_key_nsh is the stuff we transfer by netlink, kernel data path 
struct sw_flow_key also uses it. struct ovs_key_nsh is very appropriate
to do this in struct flow, it is very natural way to do right thing.

A field corresponding to multiple keys is noraml if you check ovs code,
mpls_lse is a typical case.

> > @@ -572,11 +574,12 @@ parse_nsh(const void **datap, size_t *sizep,
> > struct flow_nsh *key)
> >      switch (key->mdtype) {
> >          case NSH_M_TYPE1:
> >              for (size_t i = 0; i < 4; i++) {
> > -                key->c[i] = get_16aligned_be32(&nsh->md1.c[i]);
> > +                key->context[i] = get_16aligned_be32(&nsh->md1.context[i]);
> >              }
> >              break;
> >          case NSH_M_TYPE2:
> > -            /* Don't support MD type 2 yet, so return false */
> > +            /* Don't support MD type 2 metedata parsing yet */
> > +            break;
> >          default:
> >              return false;
>  
> [Jan] There is no need to return false here. If the md_type is unknown we don’t
> parse the context headers, but we can still continue processing the NSH packet
> as the total length is all we need.

But if md_type is unknow, it is wrong, why do we still continue it?
>  
> >      }
> > @@ -885,9 +888,7 @@ miniflow_extract(struct dp_packet *packet, struct
> > miniflow *dst)
> >                                          sizeof(uint64_t));
> >                  }
> >                  else if (nsh.mdtype == NSH_M_TYPE2) {
> > -                    /* parse_nsh has stopped it from arriving here for
> > -                     * MD type 2, will add MD type 2 support code here later
> > -                     */
> > +                    /* Can't parse MD type 2 metedata yet */
> >                  }
>  
> [Jan] If parse_nsh returns true, we always need to push the flow_nsh to
> miniflow. Please remove the extra check on MD1.

Ok, will remove extra check.

>  
> > diff --git a/lib/odp-execute.c b/lib/odp-execute.c
> > index 5f4d23a..0a9a535 100644
> > --- a/lib/odp-execute.c
> > +++ b/lib/odp-execute.c
> > @@ -273,19 +273,22 @@ odp_set_nd(struct dp_packet *packet, const
> > struct ovs_key_nd *key,
> >  /* Set the NSH header. Assumes the NSH header is present and matches
> > the
> >   * MD format of the key. The slow path must take case of that. */
> >  static void
> > -odp_set_nsh(struct dp_packet *packet, const struct ovs_key_nsh *key,
> > -            const struct ovs_key_nsh *mask)
> > +odp_set_nsh(struct dp_packet *packet, const struct flow_nsh *key,
> > +            const struct flow_nsh *mask)
> >  {
> >      struct nsh_hdr *nsh = dp_packet_l3(packet);
> > +    ovs_be32 path_hdr;
> >
> >      if (!mask) {
> >          nsh->ver_flags_len = htons(key->flags << NSH_FLAGS_SHIFT) |
> >                               (nsh->ver_flags_len & ~htons(NSH_FLAGS_MASK));
> > -        put_16aligned_be32(&nsh->path_hdr, key->path_hdr);
> > +        path_hdr = htonl((ntohl(key->spi) << NSH_SPI_SHIFT) |
> > +                         key->si);
> > +        put_16aligned_be32(&nsh->path_hdr, path_hdr);
> >          switch (nsh->md_type) {
> >              case NSH_M_TYPE1:
> >                  for (int i = 0; i < 4; i++) {
> > -                    put_16aligned_be32(&nsh->md1.c[i], key->c[i]);
> > +                    put_16aligned_be32(&nsh->md1.context[i], key->context
> [i]);
> >                  }
> >                  break;
> >              case NSH_M_TYPE2:
> > @@ -300,16 +303,24 @@ odp_set_nsh(struct dp_packet *packet, const
> > struct ovs_key_nsh *key,
> >          nsh->ver_flags_len = htons(flags << NSH_FLAGS_SHIFT) |
> >                               (nsh->ver_flags_len & ~htons(NSH_FLAGS_MASK));
> >
> > -        ovs_be32 path_hdr = get_16aligned_be32(&nsh->path_hdr);
> > -        path_hdr = key->path_hdr | (path_hdr & ~mask->path_hdr);
> > +        path_hdr = get_16aligned_be32(&nsh->path_hdr);
> > +        uint32_t spi = (ntohl(path_hdr) & NSH_SPI_MASK) >> NSH_SPI_SHIFT;
> > +        uint8_t si = (ntohl(path_hdr) & NSH_SI_MASK) >> NSH_SI_SHIFT;
> > +        uint32_t spi_mask = ntohl(mask->spi);
> > +        if (spi_mask == 0x00ffffff) {
> > +            spi_mask = UINT32_MAX;
> > +        }
> > +        spi = ntohl(key->spi) | (spi & ~spi_mask);
> > +        si = key->si | (si & ~mask->si);
> > +        path_hdr = htonl((spi << NSH_SPI_SHIFT) | si);
> >          put_16aligned_be32(&nsh->path_hdr, path_hdr);
> >          switch (nsh->md_type) {
> >              case NSH_M_TYPE1:
> >                  for (int i = 0; i < 4; i++) {
> > -                    ovs_be32 p = get_16aligned_be32(&nsh->md1.c[i]);
> > -                    ovs_be32 k = key->c[i];
> > -                    ovs_be32 m = mask->c[i];
> > -                    put_16aligned_be32(&nsh->md1.c[i], k | (p & ~m));
> > +                    ovs_be32 p = get_16aligned_be32(&nsh->md1.context[i]);
> > +                    ovs_be32 k = key->context[i];
> > +                    ovs_be32 m = mask->context[i];
> > +                    put_16aligned_be32(&nsh->md1.context[i], k | (p & ~m));
> >                  }
> >                  break;
> >              case NSH_M_TYPE2:
>  
> [Jan] The whole function could be simplified a lot when passing in a struct
> nsh_hdr instead of struct ovs_key_nsh

Jan, but we need to handle mask use case. struct nsh_hdr can't provide
mask, isn't it?

>  
> > @@ -345,9 +356,12 @@ odp_execute_set_action(struct dp_packet
> > *packet, const struct nlattr *a)
> >          odp_eth_set_addrs(packet, nl_attr_get(a), NULL);
> >          break;
> >
> > -    case OVS_KEY_ATTR_NSH:
> > -        odp_set_nsh(packet, nl_attr_get(a), NULL);
> > +    case OVS_KEY_ATTR_NSH: {
> > +        struct flow_nsh nsh;
> > +        odp_nsh_key_from_attr(a, &nsh);
>  
> [Jan] Replace this with an  odp_nsh_hdr_from_attr that directly translates into
> struct nsh_hdr

Same as above.

>  
> > +        odp_set_nsh(packet, &nsh, NULL);
> >          break;
> > +    }
> >
> >      case OVS_KEY_ATTR_IPV4:
> >          ipv4_key = nl_attr_get_unspec(a, sizeof(struct ovs_key_ipv4));
> > @@ -473,10 +487,24 @@ odp_execute_masked_set_action(struct
> > dp_packet *packet,
> >                            get_mask(a, struct ovs_key_ethernet));
> >          break;
> >
> > -    case OVS_KEY_ATTR_NSH:
> > -        odp_set_nsh(packet, nl_attr_get(a),
> > -                    get_mask(a, struct ovs_key_nsh));
> > +    case OVS_KEY_ATTR_NSH: {
> > +        struct flow_nsh nsh, nsh_mask;
> > +        size_t size = nl_attr_get_size(a) / 2;
> > +
> > +        struct nlattr attr[1 + size / sizeof(struct nlattr) + 1];
> > +        struct nlattr mask[1 + size / sizeof(struct nlattr) + 1];
> > +
> > +        mask->nla_type = attr->nla_type = nl_attr_type(a);
> > +        mask->nla_len = attr->nla_len = NLA_HDRLEN + size;
> > +        memcpy(attr + 1, (char *)(a + 1), size);
> > +        memcpy(mask + 1, (char *)(a + 1) + size, size);
>  
> [Jan] This code looks very obscure. Can you replace this with something more
> readable and safe?

Please provide a readable and safe code piece if you have, I'm not sure
where is not readable and not safe.

>  
> > +        odp_nsh_key_from_attr(attr, &nsh);
> > +        odp_nsh_key_from_attr(mask, &nsh_mask);
> > +        odp_set_nsh(packet, &nsh, &nsh_mask);
> > +
> >          break;
> > +    }
>  
>  
> > @@ -818,18 +846,21 @@ odp_execute_actions(void *dp, struct
>  
> > +        case OVS_ACTION_ATTR_PUSH_NSH: {
> > +            uint8_t buffer[256];
> > +            struct nsh_hdr *nsh_hdr = ALIGNED_CAST(struct nsh_hdr *,
> > buffer);
> > +            const struct nsh_hdr *nsh_hdr_src = nsh_hdr;
> > +            odp_nsh_hdr_from_attr(nl_attr_get(a), nsh_hdr);
>  
> [Jan] Calling this function with nl_attr_get(a) as first argument seems to
> conflict with the implementation of function odp_nsh_hdr_from_attr(), which
> uses NL_NESTED_FOR_EACH() to iterate over the nested attributes. In my tests
> that caused crashes. Please double-check.

nl_attr_get(a) just returns nested OVS_KEY_ATTR_NSH attribute,
odp_nsh_hdr_from_attr NL_NESTED_FOR_EACH nested OVS_KEY_ATTR_NSH, isn't
it?

Please remember, here the attribute is OVS_ACTION_ATTR_PUSH_NSH, not
OVS_KEY_ATTR_NSH, nl_attr_get(a) will get data of
OVS_ACTION_ATTR_PUSH_NSH.

I have verified it. Maybe you're using a wrong ovs version.

>  
> I have to stop here for today. I will hopefully continue review next week.
>  
> BR, Jan
>  
> > diff --git a/lib/odp-util.c b/lib/odp-util.c
> > index 4f1499e..3e4403a 100644
> > --- a/lib/odp-util.c
> > +++ b/lib/odp-util.c
> > @@ -129,8 +129,8 @@ odp_action_len(uint16_t type)
> >      case OVS_ACTION_ATTR_PUSH_ETH: return sizeof(struct
> > ovs_action_push_eth);
> >      case OVS_ACTION_ATTR_POP_ETH: return 0;
> >      case OVS_ACTION_ATTR_CLONE: return ATTR_LEN_VARIABLE;
> > -    case OVS_ACTION_ATTR_ENCAP_NSH: return ATTR_LEN_VARIABLE;
> > -    case OVS_ACTION_ATTR_DECAP_NSH: return 0;
> > +    case OVS_ACTION_ATTR_PUSH_NSH: return ATTR_LEN_VARIABLE;
> > +    case OVS_ACTION_ATTR_POP_NSH: return 0;
> >
> >      case OVS_ACTION_ATTR_UNSPEC:
> >      case __OVS_ACTION_ATTR_MAX:
> > @@ -264,7 +264,7 @@ format_nsh_key(struct ds *ds, const struct
> > ovs_key_nsh *key)
> >      switch (key->mdtype) {
> >          case NSH_M_TYPE1:
> >              for (int i = 0; i < 4; i++) {
> > -                ds_put_format(ds, ",c%d=0x%x", i + 1, ntohl(key->c[i]));
> > +                ds_put_format(ds, ",c%d=0x%x", i + 1, ntohl(key->context
> [i]));
> >              }
> >              break;
> >          case NSH_M_TYPE2:
> > @@ -334,41 +334,50 @@ format_nsh_key_mask(struct ds *ds, const struct
> > ovs_key_nsh *key,
> >          format_uint8_masked(ds, &first, "np", key->np, mask->np);
> >          format_be32_masked(ds, &first, "spi", htonl(spi), htonl(spi_mask));
> >          format_uint8_masked(ds, &first, "si", si, si_mask);
> > -        format_be32_masked(ds, &first, "c1", key->c[0], mask->c[0]);
> > -        format_be32_masked(ds, &first, "c2", key->c[1], mask->c[1]);
> > -        format_be32_masked(ds, &first, "c3", key->c[2], mask->c[2]);
> > -        format_be32_masked(ds, &first, "c4", key->c[3], mask->c[3]);
> > +        format_be32_masked(ds, &first, "c1", key->context[0],
> > +                           mask->context[0]);
> > +        format_be32_masked(ds, &first, "c2", key->context[1],
> > +                           mask->context[1]);
> > +        format_be32_masked(ds, &first, "c3", key->context[2],
> > +                           mask->context[2]);
> > +        format_be32_masked(ds, &first, "c4", key->context[3],
> > +                           mask->context[3]);
> >      }
> >  }
> >
> >  static void
> > -format_odp_encap_nsh_action(struct ds *ds,
> > -                            const struct ovs_action_encap_nsh *encap_nsh)
> > +format_odp_push_nsh_action(struct ds *ds,
> > +                           const struct nsh_hdr *nsh_hdr)
> >   {
> > -    uint32_t path_hdr = ntohl(encap_nsh->path_hdr);
> > +    size_t mdlen = (((ntohs(nsh_hdr->ver_flags_len) & NSH_LEN_MASK)
> > +                         >> NSH_LEN_SHIFT) << 2) - NSH_BASE_HDR_LEN;
> > +    uint32_t path_hdr = ntohl(get_16aligned_be32(&nsh_hdr->path_hdr));
> >      uint32_t spi = (path_hdr & NSH_SPI_MASK) >> NSH_SPI_SHIFT;
> >      uint8_t si = (path_hdr & NSH_SI_MASK) >> NSH_SI_SHIFT;
> > +    uint8_t flags = (ntohs(nsh_hdr->ver_flags_len) & NSH_FLAGS_MASK)
> > +                        >> NSH_FLAGS_SHIFT;
> >
> > -    ds_put_cstr(ds, "encap_nsh(");
> > -    ds_put_format(ds, "flags=%d", encap_nsh->flags);
> > -    ds_put_format(ds, ",mdtype=%d", encap_nsh->mdtype);
> > -    ds_put_format(ds, ",np=%d", encap_nsh->np);
> > +    ds_put_cstr(ds, "push_nsh(");
> > +    ds_put_format(ds, "flags=%d", flags);
> > +    ds_put_format(ds, ",mdtype=%d", nsh_hdr->md_type);
> > +    ds_put_format(ds, ",np=%d", nsh_hdr->next_proto);
> >      ds_put_format(ds, ",spi=0x%x", spi);
> >      ds_put_format(ds, ",si=%d", si);
> > -    switch (encap_nsh->mdtype) {
> > +    switch (nsh_hdr->md_type) {
> >      case NSH_M_TYPE1: {
> > -        struct nsh_md1_ctx *md1_ctx =
> > -            ALIGNED_CAST(struct nsh_md1_ctx *, encap_nsh->metadata);
> > +        const struct nsh_md1_ctx *md1_ctx = NSH_MD1_CTX(nsh_hdr);
> >          for (int i = 0; i < 4; i++) {
> >              ds_put_format(ds, ",c%d=0x%x", i + 1,
> > -                          ntohl(get_16aligned_be32(&md1_ctx->c[i])));
> > +                          ntohl(get_16aligned_be32(&md1_ctx->context[i])));
> >          }
> >          break;
> >      }
> > -    case NSH_M_TYPE2:
> > +    case NSH_M_TYPE2: {
> > +        const struct nsh_md2_tlv *md2_ctx = NSH_MD2_CTX(nsh_hdr);
> >          ds_put_cstr(ds, ",md2=");
> > -        ds_put_hex(ds, encap_nsh->metadata, encap_nsh->mdlen);
> > +        ds_put_hex(ds, md2_ctx, mdlen);
> >          break;
> > +    }
> >      default:
> >          OVS_NOT_REACHED();
> >      }
> > @@ -1057,11 +1066,16 @@ format_odp_action(struct ds *ds, const struct
> > nlattr *a,
> >      case OVS_ACTION_ATTR_CLONE:
> >          format_odp_clone_action(ds, a, portno_names);
> >          break;
> > -    case OVS_ACTION_ATTR_ENCAP_NSH:
> > -        format_odp_encap_nsh_action(ds, nl_attr_get(a));
> > +    case OVS_ACTION_ATTR_PUSH_NSH: {
> > +        uint8_t buffer[256];
> > +        struct nsh_hdr *nsh_hdr = ALIGNED_CAST(struct nsh_hdr *, buffer);
> > +        const struct nsh_hdr *nsh_hdr_src = nsh_hdr;
> > +        odp_nsh_hdr_from_attr(nl_attr_get(a), nsh_hdr);
> > +        format_odp_push_nsh_action(ds, nsh_hdr_src);
> >          break;
> > -    case OVS_ACTION_ATTR_DECAP_NSH:
> > -        ds_put_cstr(ds, "decap_nsh()");
> > +    }
> > +    case OVS_ACTION_ATTR_POP_NSH:
> > +        ds_put_cstr(ds, "pop_nsh()");
> >          break;
> >      case OVS_ACTION_ATTR_UNSPEC:
> >      case __OVS_ACTION_ATTR_MAX:
> > @@ -1780,27 +1794,74 @@ find_end:
> >      return s - s_;
> >  }
> >
> > +static void
> > +nsh_key_to_attr(struct ofpbuf *buf, const struct flow_nsh *nsh,
> > +                uint8_t * metadata, size_t md_size,
> > +                bool is_mask)
> > +{
> > +    size_t nsh_key_ofs;
> > +    struct ovs_nsh_key_base base;
> > +    struct ovs_nsh_key_md1 md1;
> > +
> > +    base.flags = nsh->flags;
> > +    base.mdtype = nsh->mdtype;
> > +    base.np = nsh->np;
> > +    base.path_hdr = htonl((ntohl(nsh->spi) << NSH_SPI_SHIFT) |
> > +                          nsh->si);
> > +
> > +    nsh_key_ofs = nl_msg_start_nested(buf, OVS_KEY_ATTR_NSH);
> > +    nl_msg_put_unspec(buf, OVS_NSH_KEY_ATTR_BASE, &base, sizeof
> > base);
> > +
> > +    if (is_mask) {
> > +        for (int i = 0; i < 4; i++) {
> > +            md1.context[i] = nsh->context[i];
> > +        }
> > +        nl_msg_put_unspec(buf, OVS_NSH_KEY_ATTR_MD1, &md1, sizeof
> > md1);
> > +    } else {
> > +        switch (nsh->mdtype) {
> > +        case NSH_M_TYPE1:
> > +            for (int i = 0; i < 4; i++) {
> > +                md1.context[i] = nsh->context[i];
> > +            }
> > +            nl_msg_put_unspec(buf, OVS_NSH_KEY_ATTR_MD1, &md1, sizeof
> > md1);
> > +            break;
> > +        case NSH_M_TYPE2:
> > +            if (metadata && md_size > 0) {
> > +                nl_msg_put_unspec(buf, OVS_NSH_KEY_ATTR_MD2, metadata,
> > +                                  md_size);
> > +            }
> > +            break;
> > +        default:
> > +            /* No match support for other MD formats yet. */
> > +            break;
> > +        }
> > +    }
> > +    nl_msg_end_nested(buf, nsh_key_ofs);
> > +}
> > +
> > +
> >  static int
> > -parse_odp_encap_nsh_action(const char *s, struct ofpbuf *actions)
> > +parse_odp_push_nsh_action(const char *s, struct ofpbuf *actions)
> >  {
> >      int n = 0;
> >      int ret = 0;
> > -    struct ovs_action_encap_nsh encap_nsh;
> > -    uint32_t spi;
> > -    uint8_t si;
> >      uint32_t cd;
> > +    struct flow_nsh nsh;
> > +    uint8_t *metadata = NULL;
> > +    uint8_t md_size = 0;
> >
> > -    if (!ovs_scan_len(s, &n, "encap_nsh(")) {
> > +    if (!ovs_scan_len(s, &n, "push_nsh(")) {
> >          ret = -EINVAL;
> >          goto out;
> >      }
> >
> >      /* The default is NSH_M_TYPE1 */
> > -    encap_nsh.flags = 0;
> > -    encap_nsh.mdtype = NSH_M_TYPE1;
> > -    encap_nsh.mdlen = NSH_M_TYPE1_MDLEN;
> > -    encap_nsh.path_hdr = htonl(255);
> > -    memset(encap_nsh.metadata, 0, NSH_M_TYPE1_MDLEN);
> > +    nsh.flags = 0;
> > +    nsh.mdtype = NSH_M_TYPE1;
> > +    nsh.np = NSH_P_ETHERNET;
> > +    nsh.spi = 0;
> > +    nsh.si = 255;
> > +    memset(nsh.context, 0, NSH_M_TYPE1_MDLEN);
> >
> >      for (;;) {
> >          n += strspn(s + n, delimiters);
> > @@ -1808,17 +1869,17 @@ parse_odp_encap_nsh_action(const char *s,
> > struct ofpbuf *actions)
> >              break;
> >          }
> >
> > -        if (ovs_scan_len(s, &n, "flags=%"SCNi8, &encap_nsh.flags)) {
> > +        if (ovs_scan_len(s, &n, "flags=%"SCNi8, &nsh.flags)) {
> >              continue;
> >          }
> > -        if (ovs_scan_len(s, &n, "mdtype=%"SCNi8, &encap_nsh.mdtype)) {
> > -            switch (encap_nsh.mdtype) {
> > +        if (ovs_scan_len(s, &n, "mdtype=%"SCNi8, &nsh.mdtype)) {
> > +            switch (nsh.mdtype) {
> >              case NSH_M_TYPE1:
> >                  /* This is the default format. */;
> >                  break;
> >              case NSH_M_TYPE2:
> >                  /* Length will be updated later. */
> > -                encap_nsh.mdlen = 0;
> > +                md_size = 0;
> >                  break;
> >              default:
> >                  ret = -EINVAL;
> > @@ -1826,65 +1887,60 @@ parse_odp_encap_nsh_action(const char *s,
> > struct ofpbuf *actions)
> >              }
> >              continue;
> >          }
> > -        if (ovs_scan_len(s, &n, "np=%"SCNi8, &encap_nsh.np)) {
> > +        if (ovs_scan_len(s, &n, "np=%"SCNi8, &nsh.np)) {
> >              continue;
> >          }
> > -        if (ovs_scan_len(s, &n, "spi=0x%"SCNx32, &spi)) {
> > -            encap_nsh.path_hdr =
> > -                    htonl(((spi << NSH_SPI_SHIFT) & NSH_SPI_MASK) |
> > -                            (ntohl(encap_nsh.path_hdr) & ~NSH_SPI_MASK));
> > +        if (ovs_scan_len(s, &n, "spi=0x%"SCNx32, &nsh.spi)) {
> > +            nsh.spi = htonl(nsh.spi);
> >              continue;
> >          }
> > -        if (ovs_scan_len(s, &n, "si=%"SCNi8, &si)) {
> > -            encap_nsh.path_hdr =
> > -                    htonl((si << NSH_SI_SHIFT) |
> > -                            (ntohl(encap_nsh.path_hdr) & ~NSH_SI_MASK));
> > +        if (ovs_scan_len(s, &n, "si=%"SCNi8, &nsh.si)) {
> >              continue;
> >          }
> > -        if (encap_nsh.mdtype == NSH_M_TYPE1) {
> > -            struct nsh_md1_ctx *md1 =
> > -                ALIGNED_CAST(struct nsh_md1_ctx *, encap_nsh.metadata);
> > +        if (nsh.mdtype == NSH_M_TYPE1) {
> >              if (ovs_scan_len(s, &n, "c1=0x%"SCNx32, &cd)) {
> > -                put_16aligned_be32(&md1->c[0], htonl(cd));
> > +                nsh.context[0] = htonl(cd);
> >                  continue;
> >              }
> >              if (ovs_scan_len(s, &n, "c2=0x%"SCNx32, &cd)) {
> > -                put_16aligned_be32(&md1->c[1], htonl(cd));
> > +                nsh.context[1] = htonl(cd);
> >                  continue;
> >              }
> >              if (ovs_scan_len(s, &n, "c3=0x%"SCNx32, &cd)) {
> > -                put_16aligned_be32(&md1->c[2], htonl(cd));
> > +                nsh.context[2] = htonl(cd);
> >                  continue;
> >              }
> >              if (ovs_scan_len(s, &n, "c4=0x%"SCNx32, &cd)) {
> > -                put_16aligned_be32(&md1->c[3], htonl(cd));
> > +                nsh.context[3] = htonl(cd);
> >                  continue;
> >              }
> >          }
> > -        else if (encap_nsh.mdtype == NSH_M_TYPE2) {
> > +        else if (nsh.mdtype == NSH_M_TYPE2) {
> >              struct ofpbuf b;
> >              char buf[512];
> >              size_t mdlen;
> >              if (ovs_scan_len(s, &n, "md2=0x%511[0-9a-fA-F]", buf)) {
> > -                ofpbuf_use_stub(&b, encap_nsh.metadata,
> > -                                OVS_ENCAP_NSH_MAX_MD_LEN);
> > +                metadata = xmalloc(NSH_M_TYPE2_MAX_LEN - 8);
> > +                ofpbuf_use_stub(&b, metadata,
> > +                                NSH_M_TYPE2_MAX_LEN - 8);
> >                  ofpbuf_put_hex(&b, buf, &mdlen);
> > -                encap_nsh.mdlen = mdlen;
> > +                md_size = mdlen;
> >                  ofpbuf_uninit(&b);
> >              }
> >              continue;
> >          }
> >      }
> >  out:
> > -    if (ret < 0) {
> > -        return ret;
> > -    } else {
> > -        size_t size = offsetof(struct ovs_action_encap_nsh, metadata)
> > -                + ROUND_UP(encap_nsh.mdlen, 4);
> > -        nl_msg_put_unspec(actions, OVS_ACTION_ATTR_ENCAP_NSH,
> > -                          &encap_nsh, size);
> > -        return n;
> > +    if (ret >= 0) {
> > +        size_t offset = nl_msg_start_nested(actions,
> > OVS_ACTION_ATTR_PUSH_NSH);
> > +        nsh_key_to_attr(actions, &nsh, metadata, md_size, false);
> > +        nl_msg_end_nested(actions, offset);
> > +        ret = n;
> >      }
> > +    if (metadata != NULL) {
> > +        free(metadata);
> > +    }
> > +    return ret;
> >  }
> >
> >  static int
> > @@ -2089,8 +2145,8 @@ parse_odp_action(const char *s, const struct
> > simap *port_names,
> >      }
> >
> >      {
> > -        if (!strncmp(s, "encap_nsh(", 10)) {
> > -            int retval = parse_odp_encap_nsh_action(s, actions);
> > +        if (!strncmp(s, "push_nsh(", 9)) {
> > +            int retval = parse_odp_push_nsh_action(s, actions);
> >              if (retval < 0) {
> >                  return retval;
> >              }
> > @@ -2100,8 +2156,8 @@ parse_odp_action(const char *s, const struct
> > simap *port_names,
> >
> >      {
> >          int n;
> > -        if (ovs_scan(s, "decap_nsh()%n", &n)) {
> > -            nl_msg_put_flag(actions, OVS_ACTION_ATTR_DECAP_NSH);
> > +        if (ovs_scan(s, "pop_nsh()%n", &n)) {
> > +            nl_msg_put_flag(actions, OVS_ACTION_ATTR_POP_NSH);
> >              return n;
> >          }
> >      }
> > @@ -2198,6 +2254,13 @@ static const struct attr_len_tbl
> > ovs_tun_key_attr_lens[OVS_TUNNEL_KEY_ATTR_MAX +
> >      [OVS_TUNNEL_KEY_ATTR_IPV6_DST]      = { .len = 16 },
> >  };
> >
> > +static const struct attr_len_tbl
> > +ovs_nsh_key_attr_lens[OVS_NSH_KEY_ATTR_MAX + 1] = {
> > +    [OVS_NSH_KEY_ATTR_BASE]     = { .len = 8 },
> > +    [OVS_NSH_KEY_ATTR_MD1]      = { .len = 16 },
> > +    [OVS_NSH_KEY_ATTR_MD2]      = { .len = ATTR_LEN_VARIABLE },
> > +};
> > +
> >  static const struct attr_len_tbl
> > ovs_flow_key_attr_lens[OVS_KEY_ATTR_MAX + 1] = {
> >      [OVS_KEY_ATTR_ENCAP]     = { .len = ATTR_LEN_NESTED },
> >      [OVS_KEY_ATTR_PRIORITY]  = { .len = 4 },
> > @@ -2229,7 +2292,9 @@ static const struct attr_len_tbl
> > ovs_flow_key_attr_lens[OVS_KEY_ATTR_MAX + 1] =
> >      [OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4] = { .len = sizeof(struct
> > ovs_key_ct_tuple_ipv4) },
> >      [OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6] = { .len = sizeof(struct
> > ovs_key_ct_tuple_ipv6) },
> >      [OVS_KEY_ATTR_PACKET_TYPE] = { .len = 4  },
> > -    [OVS_KEY_ATTR_NSH]       = { .len = sizeof(struct ovs_key_nsh) },
> > +    [OVS_KEY_ATTR_NSH]       = { .len = ATTR_LEN_NESTED,
> > +                                 .next = ovs_nsh_key_attr_lens,
> > +                                 .next_max = OVS_NSH_KEY_ATTR_MAX },
> >  };
> >
> >  /* Returns the correct length of the payload for a flow key attribute of the
> > @@ -2280,6 +2345,135 @@ ovs_frag_type_to_string(enum ovs_frag_type
> > type)
> >      }
> >  }
> >
> > +enum odp_key_fitness
> > +odp_nsh_hdr_from_attr(const struct nlattr *attr,
> > +                      struct nsh_hdr *nsh_hdr)
> > +{
> > +    unsigned int left;
> > +    const struct nlattr *a;
> > +    bool unknown = false;
> > +    uint8_t flags = 0;
> > +    size_t mdlen = 0;
> > +    bool has_md1 = false;
> > +    bool has_md2 = false;
> > +
> > +    NL_NESTED_FOR_EACH (a, left, attr) {
> > +        uint16_t type = nl_attr_type(a);
> > +        size_t len = nl_attr_get_size(a);
> > +        int expected_len = odp_key_attr_len(ovs_nsh_key_attr_lens,
> > +                                            OVS_NSH_KEY_ATTR_MAX, type);
> > +
> > +        if (len != expected_len && expected_len >= 0) {
> > +            return ODP_FIT_ERROR;
> > +        }
> > +
> > +        switch (type) {
> > +        case OVS_NSH_KEY_ATTR_BASE: {
> > +            const struct ovs_nsh_key_base *base = nl_attr_get(a);
> > +            nsh_hdr->next_proto = base->np;
> > +            nsh_hdr->md_type = base->mdtype;
> > +            put_16aligned_be32(&nsh_hdr->path_hdr, base->path_hdr);
> > +            flags = base->flags;
> > +            break;
> > +        }
> > +        case OVS_NSH_KEY_ATTR_MD1: {
> > +            const struct ovs_nsh_key_md1 *md1 = nl_attr_get(a);
> > +            struct nsh_md1_ctx *md1_dst = nsh_md1_ctx(nsh_hdr);
> > +            has_md1 = true;
> > +            mdlen = sizeof *md1;
> > +            memcpy(md1_dst, md1, mdlen);
> > +            break;
> > +        }
> > +        case OVS_NSH_KEY_ATTR_MD2: {
> > +            struct nsh_md2_tlv *md2_dst = nsh_md2_ctx(nsh_hdr);
> > +            const uint8_t *md2 = nl_attr_get(a);
> > +            has_md2 = true;
> > +            mdlen = nl_attr_get_size(a);
> > +            memcpy(md2_dst, md2, mdlen);
> > +            break;
> > +        }
> > +        default:
> > +            /* Allow this to show up as unexpected, if there are unknown
> > +             * tunnel attribute, eventually resulting in ODP_FIT_TOO_MUCH.
> > */
> > +            unknown = true;
> > +            break;
> > +        }
> > +    }
> > +
> > +    if (unknown) {
> > +        return ODP_FIT_TOO_MUCH;
> > +    }
> > +
> > +    if ((has_md1 && nsh_hdr->md_type != NSH_M_TYPE1)
> > +        || (has_md2 && nsh_hdr->md_type != NSH_M_TYPE2)) {
> > +        return ODP_FIT_ERROR;
> > +    }
> > +
> > +    /* nsh header length  = NSH_BASE_HDR_LEN + mdlen */
> > +    nsh_hdr->ver_flags_len = htons(flags << NSH_FLAGS_SHIFT |
> > +                               (NSH_BASE_HDR_LEN + mdlen) >> 2);
> > +
> > +    return ODP_FIT_PERFECT;
> > +}
> > +
> > +enum odp_key_fitness
> > +odp_nsh_key_from_attr(const struct nlattr *attr, struct flow_nsh *nsh)
> > +{
> > +    unsigned int left;
> > +    const struct nlattr *a;
> > +    bool unknown = false;
> > +    bool has_md1 = false;
> > +
> > +    NL_NESTED_FOR_EACH (a, left, attr) {
> > +        uint16_t type = nl_attr_type(a);
> > +        size_t len = nl_attr_get_size(a);
> > +        int expected_len = odp_key_attr_len(ovs_nsh_key_attr_lens,
> > +                                            OVS_NSH_KEY_ATTR_MAX, type);
> > +
> > +        if (len != expected_len && expected_len >= 0) {
> > +            return ODP_FIT_ERROR;
> > +        }
> > +
> > +        switch (type) {
> > +        case OVS_NSH_KEY_ATTR_BASE: {
> > +            const struct ovs_nsh_key_base *base = nl_attr_get(a);
> > +            nsh->flags = base->flags;
> > +            nsh->mdtype = base->mdtype;
> > +            nsh->np = base->np;
> > +            nsh->spi = htonl((ntohl(base->path_hdr) & NSH_SPI_MASK) >>
> > +                                 NSH_SPI_SHIFT);
> > +            nsh->si = (ntohl(base->path_hdr) & NSH_SI_MASK) >>
> > NSH_SI_SHIFT;
> > +            break;
> > +        }
> > +        case OVS_NSH_KEY_ATTR_MD1: {
> > +            const struct ovs_nsh_key_md1 *md1 = nl_attr_get(a);
> > +            has_md1 = true;
> > +            nsh->context[0] = md1->context[0];
> > +            nsh->context[1] = md1->context[1];
> > +            nsh->context[2] = md1->context[2];
> > +            nsh->context[3] = md1->context[3];
> > +            break;
> > +        }
> > +        case OVS_NSH_KEY_ATTR_MD2:
> > +        default:
> > +            /* Allow this to show up as unexpected, if there are unknown
> > +             * tunnel attribute, eventually resulting in ODP_FIT_TOO_MUCH.
> > */
> > +            unknown = true;
> > +            break;
> > +        }
> > +    }
> > +
> > +    if (unknown) {
> > +        return ODP_FIT_TOO_MUCH;
> > +    }
> > +
> > +    if (has_md1 && nsh->mdtype != NSH_M_TYPE1) {
> > +        return ODP_FIT_ERROR;
> > +    }
> > +
> > +    return ODP_FIT_PERFECT;
> > +}
> > +
> >  static enum odp_key_fitness
> >  odp_tun_key_from_attr__(const struct nlattr *attr, bool is_mask,
> >                          struct flow_tnl *tun)
> > @@ -2971,6 +3165,80 @@ format_odp_tun_geneve(const struct nlattr
> > *attr,
> >  }
> >
> >  static void
> > +format_odp_nsh_attr(const struct nlattr *attr, const struct nlattr
> > *mask_attr,
> > +                    struct ds *ds)
> > +{
> > +    unsigned int left;
> > +    const struct nlattr *a;
> > +    struct ovs_key_nsh nsh;
> > +    struct ovs_key_nsh nsh_mask;
> > +
> > +    memset(&nsh, 0, sizeof nsh);
> > +    memset(&nsh_mask, 0xff, sizeof nsh_mask);
> > +
> > +    NL_NESTED_FOR_EACH (a, left, attr) {
> > +        enum ovs_nsh_key_attr type = nl_attr_type(a);
> > +        const struct nlattr *ma = NULL;
> > +
> > +        if (mask_attr) {
> > +            ma = nl_attr_find__(nl_attr_get(mask_attr),
> > +                                nl_attr_get_size(mask_attr), type);
> > +        }
> > +
> > +        if (!check_attr_len(ds, a, ma, ovs_nsh_key_attr_lens,
> > +                            OVS_NSH_KEY_ATTR_MAX, true)) {
> > +            continue;
> > +        }
> > +
> > +        switch (type) {
> > +        case OVS_NSH_KEY_ATTR_BASE: {
> > +            const struct ovs_nsh_key_base * base = nl_attr_get(a);
> > +            const struct ovs_nsh_key_base * base_mask
> > +                = ma ? nl_attr_get(ma) : NULL;
> > +            nsh.flags = base->flags;
> > +            nsh.mdtype = base->mdtype;
> > +            nsh.np = base->np;
> > +            nsh.path_hdr = base->path_hdr;
> > +            if (base_mask) {
> > +                nsh_mask.flags = base_mask->flags;
> > +                nsh_mask.mdtype = base_mask->mdtype;
> > +                nsh_mask.np = base_mask->np;
> > +                nsh_mask.path_hdr = base_mask->path_hdr;
> > +            }
> > +            break;
> > +        }
> > +        case OVS_NSH_KEY_ATTR_MD1: {
> > +            const struct ovs_nsh_key_md1 * md1 = nl_attr_get(a);
> > +            const struct ovs_nsh_key_md1 * md1_mask
> > +                = ma ? nl_attr_get(ma) : NULL;
> > +            nsh.context[0] = md1->context[0];
> > +            nsh.context[1] = md1->context[1];
> > +            nsh.context[2] = md1->context[2];
> > +            nsh.context[3] = md1->context[3];
> > +            if (md1_mask) {
> > +                nsh_mask.context[0] = md1_mask->context[0];
> > +                nsh_mask.context[1] = md1_mask->context[1];
> > +                nsh_mask.context[2] = md1_mask->context[2];
> > +                nsh_mask.context[3] = md1_mask->context[3];
> > +            }
> > +            break;
> > +        }
> > +        case OVS_NSH_KEY_ATTR_MD2:
> > +        case __OVS_NSH_KEY_ATTR_MAX:
> > +        default:
> > +            /* No support for matching other metadata formats yet. */
> > +            break;
> > +        }
> > +    }
> > +
> > +    if (mask_attr) {
> > +        format_nsh_key_mask(ds, &nsh, &nsh_mask);
> > +    } else {
> > +        format_nsh_key(ds, &nsh);
> > +    }
> > +}
> > +
> > +static void
> >  format_odp_tun_attr(const struct nlattr *attr, const struct nlattr
> > *mask_attr,
> >                      struct ds *ds, bool verbose)
> >  {
> > @@ -3448,9 +3716,7 @@ format_odp_key_attr__(const struct nlattr *a,
> > const struct nlattr *ma,
> >          break;
> >      }
> >      case OVS_KEY_ATTR_NSH: {
> > -        const struct ovs_key_nsh *mask = ma ? nl_attr_get(ma) : NULL;
> > -        const struct ovs_key_nsh *key = nl_attr_get(a);
> > -        format_nsh_key_mask(ds, key, mask);
> > +        format_odp_nsh_attr(a, ma, ds);
> >          break;
> >      }
> >      case OVS_KEY_ATTR_UNSPEC:
> > @@ -4549,6 +4815,129 @@ geneve_to_attr(struct ofpbuf *a, const void
> > *data_)
> >      } SCAN_END_SINGLE(ATTR)
> >
> >  static int
> > +parse_odp_nsh_key_mask_attr(const char *s, struct ofpbuf *key,
> > +                            struct ofpbuf *mask)
> > +{
> > +    if (strncmp(s, "nsh(", 4) == 0) {
> > +        const char *start = s;
> > +        int len;
> > +        struct flow_nsh skey, smask;
> > +
> > +        s += 4;
> > +
> > +        memset(&skey, 0, sizeof skey);
> > +        memset(&smask, 0, sizeof smask);
> > +        do {
> > +            len = 0;
> > +
> > +            if (strncmp(s, "flags=", 6) == 0) {
> > +                s += 6;
> > +                len = scan_u8(s, &skey.flags, mask ? &smask.flags : NULL);
> > +                if (len == 0) {
> > +                    return -EINVAL;
> > +                }
> > +                s += len;
> > +                continue;
> > +            }
> > +
> > +            if (strncmp(s, "mdtype=", 7) == 0) {
> > +                s += 7;
> > +                len = scan_u8(s, &skey.mdtype, mask ? &smask.mdtype : NULL);
> > +                if (len == 0) {
> > +                    return -EINVAL;
> > +                }
> > +                s += len;
> > +                continue;
> > +            }
> > +
> > +            if (strncmp(s, "np=", 3) == 0) {
> > +                s += 3;
> > +                len = scan_u8(s, &skey.np, mask ? &smask.np : NULL);
> > +                if (len == 0) {
> > +                    return -EINVAL;
> > +                }
> > +                s += len;
> > +                continue;
> > +            }
> > +
> > +            if (strncmp(s, "spi=", 4) == 0) {
> > +                s += 4;
> > +                len = scan_be32(s, &skey.spi, mask ? &smask.spi : NULL);
> > +                if (len == 0) {
> > +                    return -EINVAL;
> > +                }
> > +                s += len;
> > +                continue;
> > +            }
> > +
> > +            if (strncmp(s, "si=", 3) == 0) {
> > +                s += 3;
> > +                len = scan_u8(s, &skey.si, mask ? &smask.si : NULL);
> > +                if (len == 0) {
> > +                    return -EINVAL;
> > +                }
> > +                s += len;
> > +                continue;
> > +            }
> > +
> > +            if (strncmp(s, "c1=", 3) == 0) {
> > +                s += 3;
> > +                len = scan_be32(s, &skey.context[0],
> > +                                mask ? &smask.context[0] : NULL);
> > +                if (len == 0) {
> > +                    return -EINVAL;
> > +                }
> > +                s += len;
> > +                continue;
> > +            }
> > +
> > +            if (strncmp(s, "c2=", 3) == 0) {
> > +                s += 3;
> > +                len = scan_be32(s, &skey.context[1],
> > +                                mask ? &smask.context[1] : NULL);
> > +                if (len == 0) {
> > +                    return -EINVAL;
> > +                }
> > +                s += len;
> > +                continue;
> > +            }
> > +
> > +            if (strncmp(s, "c3=", 3) == 0) {
> > +                s += 3;
> > +                len = scan_be32(s, &skey.context[2],
> > +                                mask ? &smask.context[2] : NULL);
> > +                if (len == 0) {
> > +                    return -EINVAL;
> > +                }
> > +                s += len;
> > +                continue;
> > +            }
> > +
> > +            if (strncmp(s, "c4=", 3) == 0) {
> > +                s += 3;
> > +                len = scan_be32(s, &skey.context[3],
> > +                                mask ? &smask.context[3] : NULL);
> > +                if (len == 0) {
> > +                    return -EINVAL;
> > +                }
> > +                s += len;
> > +                continue;
> > +            }
> > +        } while (*s++ == ',' && len != 0);
> > +        if (s[-1] != ')') {
> > +            return -EINVAL;
> > +        }
> > +
> > +        nsh_key_to_attr(key, &skey, NULL, 0, false);
> > +        if (mask) {
> > +            nsh_key_to_attr(mask, &smask, NULL, 0, true);
> > +        }
> > +        return s - start;
> > +    }
> > +    return 0;
> > +}
> > +
> > +static int
> >  parse_odp_key_mask_attr(const char *s, const struct simap *port_names,
> >                          struct ofpbuf *key, struct ofpbuf *mask)
> >  {
> > @@ -4694,16 +5083,13 @@ parse_odp_key_mask_attr(const char *s,
> > const struct simap *port_names,
> >          SCAN_FIELD("id=", be16, id);
> >      } SCAN_END(OVS_KEY_ATTR_PACKET_TYPE);
> >
> > -    SCAN_BEGIN("nsh(", struct ovs_key_nsh) {
> > -        SCAN_FIELD("flags=", u8, flags);
> > -        SCAN_FIELD("mdtype=", u8, mdtype);
> > -        SCAN_FIELD("np=", u8, np);
> > -        SCAN_FIELD("path_hdr=", be32, path_hdr);
> > -        SCAN_FIELD("c1=", be32, c[0]);
> > -        SCAN_FIELD("c2=", be32, c[1]);
> > -        SCAN_FIELD("c3=", be32, c[2]);
> > -        SCAN_FIELD("c4=", be32, c[2]);
> > -    } SCAN_END(OVS_KEY_ATTR_NSH);
> > +    /* nsh is nested, it needs special process */
> > +    int ret = parse_odp_nsh_key_mask_attr(s, key, mask);
> > +    if (ret < 0) {
> > +       return ret;
> > +    } else {
> > +       s += ret;
> > +    }
> >
> >      /* Encap open-coded. */
> >      if (!strncmp(s, "encap(", 6)) {
> > @@ -4994,11 +5380,7 @@ odp_flow_key_from_flow__(const struct
> > odp_flow_key_parms *parms,
> >              mpls_key[i].mpls_lse = data->mpls_lse[i];
> >          }
> >      } else if (flow->dl_type == htons(ETH_TYPE_NSH)) {
> > -        struct ovs_key_nsh *nsh_key;
> > -
> > -        nsh_key = nl_msg_put_unspec_uninit(buf, OVS_KEY_ATTR_NSH,
> > -                                            sizeof *nsh_key);
> > -        get_nsh_key(data, nsh_key, export_mask);
> > +        nsh_key_to_attr(buf, &data->nsh, NULL, 0, export_mask);
> >      }
> >
> >      if (is_ip_any(flow) && !(flow->nw_frag & FLOW_NW_FRAG_LATER)) {
> > @@ -5558,13 +5940,10 @@ parse_l2_5_onward(const struct nlattr
> > *attrs[OVS_KEY_ATTR_MAX + 1],
> >              expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_NSH;
> >          }
> >          if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_NSH)) {
> > -            const struct ovs_key_nsh *nsh_key;
> > -
> > -            nsh_key = nl_attr_get(attrs[OVS_KEY_ATTR_NSH]);
> > -            put_nsh_key(nsh_key, flow, false);
> > +            odp_nsh_key_from_attr(attrs[OVS_KEY_ATTR_NSH], &flow->nsh);
> >              if (is_mask) {
> > -                check_start = nsh_key;
> > -                check_len = sizeof *nsh_key;
> > +                check_start = nl_attr_get(attrs[OVS_KEY_ATTR_NSH]);
> > +                check_len = nl_attr_get_size(attrs[OVS_KEY_ATTR_NSH]);
> >                  expected_bit = OVS_KEY_ATTR_NSH;
> >              }
> >          }
> > @@ -6620,13 +6999,13 @@ get_nsh_key(const struct flow *flow, struct
> > ovs_key_nsh *nsh, bool is_mask)
> >                            flow->nsh.si);
> >      if (is_mask) {
> >          for (int i = 0; i < 4; i++) {
> > -            nsh->c[i] = flow->nsh.c[i];
> > +            nsh->context[i] = flow->nsh.context[i];
> >          }
> >      } else {
> >          switch (nsh->mdtype) {
> >          case NSH_M_TYPE1:
> >              for (int i = 0; i < 4; i++) {
> > -                nsh->c[i] = flow->nsh.c[i];
> > +                nsh->context[i] = flow->nsh.context[i];
> >              }
> >              break;
> >          case NSH_M_TYPE2:
> > @@ -6650,17 +7029,125 @@ put_nsh_key(const struct ovs_key_nsh *nsh,
> > struct flow *flow,
> >      switch (nsh->mdtype) {
> >          case NSH_M_TYPE1:
> >              for (int i = 0; i < 4; i++) {
> > -                flow->nsh.c[i] = nsh->c[i];
> > +                flow->nsh.context[i] = nsh->context[i];
> >              }
> >              break;
> >          case NSH_M_TYPE2:
> >          default:
> >              /* No match support for other MD formats yet. */
> > -            memset(flow->nsh.c, 0, sizeof flow->nsh.c);
> > +            memset(flow->nsh.context, 0, sizeof flow->nsh.context);
> >              break;
> >      }
> >  }
> >
> > +static bool
> > +commit_nsh(const struct flow_nsh * flow_nsh, bool use_masked_set,
> > +           const struct ovs_key_nsh *key, struct ovs_key_nsh *base,
> > +           struct ovs_key_nsh *mask, size_t size,
> > +           struct ofpbuf *odp_actions)
> > +{
> > +    enum ovs_key_attr attr = OVS_KEY_ATTR_NSH;
> > +
> > +    if (memcmp(key, base, size)  == 0) {
> > +        /* Mask bits are set when we have either read or set the
> > corresponding
> > +         * values.  Masked bits will be exact-matched, no need to set them
> > +         * if the value did not actually change. */
> > +        return false;
> > +    }
> > +
> > +    bool fully_masked = odp_mask_is_exact(attr, mask, size);
> > +
> > +    if (use_masked_set && !fully_masked) {
> > +        size_t nsh_key_ofs;
> > +        struct ovs_nsh_key_base nsh_base;
> > +        struct ovs_nsh_key_base nsh_base_mask;
> > +        struct ovs_nsh_key_md1 md1;
> > +        struct ovs_nsh_key_md1 md1_mask;
> > +        size_t offset = nl_msg_start_nested(odp_actions,
> > +                                            OVS_ACTION_ATTR_SET_MASKED);
> > +
> > +        nsh_base.flags = key->flags;
> > +        nsh_base.mdtype = key->mdtype;
> > +        nsh_base.np = key->np;
> > +        nsh_base.path_hdr = key->path_hdr;
> > +
> > +        nsh_base_mask.flags = mask->flags;
> > +        nsh_base_mask.mdtype = mask->mdtype;
> > +        nsh_base_mask.np = mask->np;
> > +        nsh_base_mask.path_hdr = mask->path_hdr;
> > +
> > +        /* OVS_KEY_ATTR_NSH keys */
> > +        nsh_key_ofs = nl_msg_start_nested(odp_actions,
> > OVS_KEY_ATTR_NSH);
> > +
> > +        char *data = nl_msg_put_unspec_uninit(odp_actions,
> > +                                              OVS_NSH_KEY_ATTR_BASE,
> > +                                              sizeof(nsh_base));
> > +        const char *lkey = (char *)&nsh_base, *lmask = (char
> > *)&nsh_base_mask;
> > +        size_t lkey_size = sizeof(nsh_base);
> > +
> > +        while (lkey_size--) {
> > +            *data++ = *lkey++ & *lmask++;
> > +        }
> > +
> > +        switch (key->mdtype) {
> > +        case NSH_M_TYPE1:
> > +            for (int i = 0; i < 4; i++) {
> > +                md1.context[i] = key->context[i];
> > +                md1_mask.context[i] = mask->context[i];
> > +            }
> > +            data = nl_msg_put_unspec_uninit(odp_actions,
> > +                                            OVS_NSH_KEY_ATTR_MD1,
> > +                                            sizeof(md1));
> > +            lkey = (char *)&md1;
> > +            lmask = (char *)&md1_mask;
> > +            lkey_size = sizeof(md1);
> > +
> > +            while (lkey_size--) {
> > +                *data++ = *lkey++ & *lmask++;
> > +            }
> > +            break;
> > +        case NSH_M_TYPE2:
> > +        default:
> > +            /* No match support for other MD formats yet. */
> > +            break;
> > +        }
> > +
> > +        /* OVS_KEY_ATTR_NSH masks */
> > +        data = nl_msg_put_unspec_uninit(odp_actions,
> > +                                        OVS_NSH_KEY_ATTR_BASE,
> > +                                        sizeof(nsh_base_mask));
> > +        lmask = (char *)&nsh_base_mask;
> > +
> > +        memcpy(data, lmask, sizeof(nsh_base_mask));
> > +
> > +        switch (key->mdtype) {
> > +        case NSH_M_TYPE1:
> > +            data = nl_msg_put_unspec_uninit(odp_actions,
> > +                                            OVS_NSH_KEY_ATTR_MD1,
> > +                                            sizeof(md1_mask));
> > +            lmask = (char *)&md1_mask;
> > +            memcpy(data, lmask, sizeof(md1_mask));
> > +            break;
> > +        case NSH_M_TYPE2:
> > +        default:
> > +            /* No match support for other MD formats yet. */
> > +            break;
> > +        }
> > +        nl_msg_end_nested(odp_actions, nsh_key_ofs);
> > +
> > +        nl_msg_end_nested(odp_actions, offset);
> > +    } else {
> > +        if (!fully_masked) {
> > +            memset(mask, 0xff, size);
> > +        }
> > +        size_t offset = nl_msg_start_nested(odp_actions,
> > OVS_ACTION_ATTR_SET);
> > +        nsh_key_to_attr(odp_actions, flow_nsh, NULL, 0, false);
> > +        nl_msg_end_nested(odp_actions, offset);
> > +    }
> > +    memcpy(base, key, size);
> > +    return true;
> > +}
> > +
> >  static void
> >  commit_set_nsh_action(const struct flow *flow, struct flow *base_flow,
> >                        struct ofpbuf *odp_actions,
> > @@ -6684,8 +7171,8 @@ commit_set_nsh_action(const struct flow *flow,
> > struct flow *base_flow,
> >      mask.mdtype = 0;     /* Not writable. */
> >      mask.np = 0;         /* Not writable. */
> >
> > -    if (commit(OVS_KEY_ATTR_NSH, use_masked, &key, &base, &mask,
> > sizeof key,
> > -               odp_actions)) {
> > +    if (commit_nsh(&base_flow->nsh, use_masked, &key, &base, &mask,
> > +            sizeof key, odp_actions)) {
> >          put_nsh_key(&base, base_flow, false);
> >          if (mask.mdtype != 0) { /* Mask was changed by commit(). */
> >              put_nsh_key(&mask, &wc->masks, true);
> > @@ -6788,49 +7275,36 @@ commit_set_pkt_mark_action(const struct
> > flow *flow, struct flow *base_flow,
> >  }
> >
> >  static void
> > -odp_put_decap_nsh_action(struct ofpbuf *odp_actions)
> > +odp_put_pop_nsh_action(struct ofpbuf *odp_actions)
> >  {
> > -    nl_msg_put_flag(odp_actions, OVS_ACTION_ATTR_DECAP_NSH);
> > +    nl_msg_put_flag(odp_actions, OVS_ACTION_ATTR_POP_NSH);
> >  }
> >
> >  static void
> > -odp_put_encap_nsh_action(struct ofpbuf *odp_actions,
> > +odp_put_push_nsh_action(struct ofpbuf *odp_actions,
> >                           const struct flow *flow,
> >                           struct ofpbuf *encap_data)
> >  {
> > -    struct ovs_action_encap_nsh encap_nsh;
> > -
> > -    encap_nsh.flags = flow->nsh.flags;
> > -    encap_nsh.mdtype = flow->nsh.mdtype;
> > -    encap_nsh.np = flow->nsh.np;
> > -    encap_nsh.path_hdr = htonl((ntohl(flow->nsh.spi) << NSH_SPI_SHIFT) |
> > -                                   flow->nsh.si);
> > +    uint8_t * metadata = NULL;
> > +    uint8_t md_size = 0;
> >
> > -    switch (encap_nsh.mdtype) {
> > -    case NSH_M_TYPE1: {
> > -        struct nsh_md1_ctx *md1 =
> > -            ALIGNED_CAST(struct nsh_md1_ctx *, encap_nsh.metadata);
> > -        encap_nsh.mdlen = NSH_M_TYPE1_MDLEN;
> > -        for (int i = 0; i < 4; i++) {
> > -            put_16aligned_be32(&md1->c[i], flow->nsh.c[i]);
> > -        }
> > -        break;
> > -    }
> > +    switch (flow->nsh.mdtype) {
> >      case NSH_M_TYPE2:
> >          if (encap_data) {
> > -            ovs_assert(encap_data->size < OVS_ENCAP_NSH_MAX_MD_LEN);
> > -            encap_nsh.mdlen = encap_data->size;
> > -            memcpy(encap_nsh.metadata, encap_data->data, encap_data-
> > >size);
> > +            ovs_assert(encap_data->size < OVS_PUSH_NSH_MAX_MD_LEN);
> > +            metadata = encap_data->data;
> > +            md_size = encap_data->size;
> >          } else {
> > -            encap_nsh.mdlen = 0;
> > +            md_size = 0;
> >          }
> >          break;
> >      default:
> > -        encap_nsh.mdlen = 0;
> > +        md_size = 0;
> >          break;
> >      }
> > -    nl_msg_put_unspec(odp_actions, OVS_ACTION_ATTR_ENCAP_NSH,
> > -                      &encap_nsh, sizeof(encap_nsh));
> > +    size_t offset = nl_msg_start_nested(odp_actions,
> > OVS_ACTION_ATTR_PUSH_NSH);
> > +    nsh_key_to_attr(odp_actions, &flow->nsh, metadata, md_size, false);
> > +    nl_msg_end_nested(odp_actions, offset);
> >  }
> >
> >  static void
> > @@ -6857,8 +7331,8 @@ commit_packet_type_change(const struct flow
> > *flow,
> >              break;
> >          }
> >          case PT_NSH:
> > -            /* encap_nsh */
> > -            odp_put_encap_nsh_action(odp_actions, flow, encap_data);
> > +            /* push_nsh */
> > +            odp_put_push_nsh_action(odp_actions, flow, encap_data);
> >              base_flow->packet_type = flow->packet_type;
> >              /* Update all packet headers in base_flow. */
> >              memcpy(&base_flow->dl_dst, &flow->dl_dst,
> > @@ -6883,8 +7357,8 @@ commit_packet_type_change(const struct flow
> > *flow,
> >               * No need to update the base flow here. */
> >              switch (ntohl(base_flow->packet_type)) {
> >              case PT_NSH:
> > -                /* decap_nsh. */
> > -                odp_put_decap_nsh_action(odp_actions);
> > +                /* pop_nsh. */
> > +                odp_put_pop_nsh_action(odp_actions);
> >                  break;
> >              default:
> >                  /* Checks are done during translation. */
> > diff --git a/lib/odp-util.h b/lib/odp-util.h
> > index 27c2ab4..bd6be60 100644
> > --- a/lib/odp-util.h
> > +++ b/lib/odp-util.h
> > @@ -158,6 +158,10 @@ struct odputil_keybuf {
> >
> >  enum odp_key_fitness odp_tun_key_from_attr(const struct nlattr *,
> >                                             struct flow_tnl *);
> > +enum odp_key_fitness odp_nsh_key_from_attr(const struct nlattr *,
> > +                                           struct flow_nsh *);
> > +enum odp_key_fitness odp_nsh_hdr_from_attr(const struct nlattr *,
> > +                                           struct nsh_hdr *);
> >
> >  int odp_ufid_from_string(const char *s_, ovs_u128 *ufid);
> >  void odp_format_ufid(const ovs_u128 *ufid, struct ds *);
> > diff --git a/lib/packets.c b/lib/packets.c
> > index 74d87ed..db98ab3 100644
> > --- a/lib/packets.c
> > +++ b/lib/packets.c
> > @@ -403,10 +403,10 @@ pop_mpls(struct dp_packet *packet, ovs_be16
> > ethtype)
> >  }
> >
> >  void
> > -encap_nsh(struct dp_packet *packet, const struct ovs_action_encap_nsh
> > *encap)
> > +push_nsh(struct dp_packet *packet, const struct nsh_hdr *nsh_hdr_src)
> >  {
> >      struct nsh_hdr *nsh;
> > -    size_t length = NSH_BASE_HDR_LEN + encap->mdlen;
> > +    size_t length = nsh_hdr_len(nsh_hdr_src);
> >      uint8_t next_proto;
> >
> >      switch (ntohl(packet->packet_type)) {
> > @@ -427,23 +427,8 @@ encap_nsh(struct dp_packet *packet, const struct
> > ovs_action_encap_nsh *encap)
> >      }
> >
> >      nsh = (struct nsh_hdr *) dp_packet_push_uninit(packet, length);
> > -    nsh->ver_flags_len = htons(encap->flags << NSH_FLAGS_SHIFT | length
> > >> 2);
> > +    memcpy(nsh, nsh_hdr_src, length);
> >      nsh->next_proto = next_proto;
> > -    put_16aligned_be32(&nsh->path_hdr, encap->path_hdr);
> > -    nsh->md_type = encap->mdtype;
> > -    switch (nsh->md_type) {
> > -        case NSH_M_TYPE1:
> > -            nsh->md1 = *ALIGNED_CAST(struct nsh_md1_ctx *, encap-
> > >metadata);
> > -            break;
> > -        case NSH_M_TYPE2: {
> > -            /* The MD2 metadata in encap is already padded to 4 bytes. */
> > -            size_t len = ROUND_UP(encap->mdlen, 4);
> > -            memcpy(&nsh->md2, encap->metadata, len);
> > -            break;
> > -        }
> > -        default:
> > -            OVS_NOT_REACHED();
> > -    }
> >
> >      packet->packet_type = htonl(PT_NSH);
> >      dp_packet_reset_offsets(packet);
> > @@ -451,7 +436,7 @@ encap_nsh(struct dp_packet *packet, const struct
> > ovs_action_encap_nsh *encap)
> >  }
> >
> >  bool
> > -decap_nsh(struct dp_packet *packet)
> > +pop_nsh(struct dp_packet *packet)
> >  {
> >      struct nsh_hdr *nsh = (struct nsh_hdr *) dp_packet_l3(packet);
> >      size_t length;
> > diff --git a/lib/packets.h b/lib/packets.h
> > index 705d0b2..e7832ba 100644
> > --- a/lib/packets.h
> > +++ b/lib/packets.h
> > @@ -434,9 +434,8 @@ void push_eth(struct dp_packet *packet, const
> > struct eth_addr *dst,
> >                const struct eth_addr *src);
> >  void pop_eth(struct dp_packet *packet);
> >
> > -void encap_nsh(struct dp_packet *packet,
> > -               const struct ovs_action_encap_nsh *encap_nsh);
> > -bool decap_nsh(struct dp_packet *packet);
> > +void push_nsh(struct dp_packet *packet, const struct nsh_hdr
> > *nsh_hdr_src);
> > +bool pop_nsh(struct dp_packet *packet);
> >
> >  #define LLC_DSAP_SNAP 0xaa
> >  #define LLC_SSAP_SNAP 0xaa
> > diff --git a/ofproto/ofproto-dpif-ipfix.c b/ofproto/ofproto-dpif-ipfix.c
> > index 472c272..16976c4 100644
> > --- a/ofproto/ofproto-dpif-ipfix.c
> > +++ b/ofproto/ofproto-dpif-ipfix.c
> > @@ -2823,8 +2823,8 @@ dpif_ipfix_read_actions(const struct flow *flow,
> >          case OVS_ACTION_ATTR_POP_MPLS:
> >          case OVS_ACTION_ATTR_PUSH_ETH:
> >          case OVS_ACTION_ATTR_POP_ETH:
> > -        case OVS_ACTION_ATTR_ENCAP_NSH:
> > -        case OVS_ACTION_ATTR_DECAP_NSH:
> > +        case OVS_ACTION_ATTR_PUSH_NSH:
> > +        case OVS_ACTION_ATTR_POP_NSH:
> >          case OVS_ACTION_ATTR_UNSPEC:
> >          case __OVS_ACTION_ATTR_MAX:
> >          default:
> > diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
> > index 65a2003..1af1569 100644
> > --- a/ofproto/ofproto-dpif-sflow.c
> > +++ b/ofproto/ofproto-dpif-sflow.c
> > @@ -1199,8 +1199,8 @@ dpif_sflow_read_actions(const struct flow *flow,
> >            break;
> >        case OVS_ACTION_ATTR_SAMPLE:
> >        case OVS_ACTION_ATTR_CLONE:
> > -        case OVS_ACTION_ATTR_ENCAP_NSH:
> > -        case OVS_ACTION_ATTR_DECAP_NSH:
> > +        case OVS_ACTION_ATTR_PUSH_NSH:
> > +        case OVS_ACTION_ATTR_POP_NSH:
> >        case OVS_ACTION_ATTR_UNSPEC:
> >        case __OVS_ACTION_ATTR_MAX:
> >        default:
> > diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
> > index 973e760..437c519 100644
> > --- a/ofproto/ofproto-dpif-xlate.c
> > +++ b/ofproto/ofproto-dpif-xlate.c
> > @@ -4392,8 +4392,8 @@ xlate_fixup_actions(struct ofpbuf *b, const struct
> > nlattr *actions,
> >          case OVS_ACTION_ATTR_CT:
> >          case OVS_ACTION_ATTR_PUSH_ETH:
> >          case OVS_ACTION_ATTR_POP_ETH:
> > -        case OVS_ACTION_ATTR_ENCAP_NSH:
> > -        case OVS_ACTION_ATTR_DECAP_NSH:
> > +        case OVS_ACTION_ATTR_PUSH_NSH:
> > +        case OVS_ACTION_ATTR_POP_NSH:
> >          case OVS_ACTION_ATTR_METER:
> >              ofpbuf_put(b, a, nl_attr_len_pad(a, left));
> >              break;
> > @@ -5804,17 +5804,17 @@ rewrite_flow_encap_ethernet(struct xlate_ctx
> > *ctx,
> >
> >  /* For an MD2 NSH header returns a pointer to an ofpbuf with the
> > encoded
> >   * MD2 TLVs provided as encap properties to the encap operation. This
> > - * will be stored as encap_data in the ctx and copied into the encap_nsh
> > + * will be stored as encap_data in the ctx and copied into the push_nsh
> >   * action at the next commit. */
> >  static struct ofpbuf *
> > -rewrite_flow_encap_nsh(struct xlate_ctx *ctx,
> > -                       const struct ofpact_encap *encap,
> > -                       struct flow *flow,
> > -                       struct flow_wildcards *wc)
> > +rewrite_flow_push_nsh(struct xlate_ctx *ctx,
> > +                      const struct ofpact_encap *encap,
> > +                      struct flow *flow,
> > +                      struct flow_wildcards *wc)
> >  {
> >      ovs_be32 packet_type = flow->packet_type;
> >      const char *ptr = (char *) encap->props;
> > -    struct ofpbuf *buf = ofpbuf_new(OVS_ENCAP_NSH_MAX_MD_LEN);
> > +    struct ofpbuf *buf = ofpbuf_new(OVS_PUSH_NSH_MAX_MD_LEN);
> >      uint8_t md_type = NSH_M_TYPE1;
> >      uint8_t np = 0;
> >      int i;
> > @@ -5854,7 +5854,7 @@ rewrite_flow_encap_nsh(struct xlate_ctx *ctx,
> >          }
> >          ptr += ROUND_UP(prop_ptr->len, 8);
> >      }
> > -    if (buf->size == 0 || buf->size > OVS_ENCAP_NSH_MAX_MD_LEN) {
> > +    if (buf->size == 0 || buf->size > OVS_PUSH_NSH_MAX_MD_LEN) {
> >          ofpbuf_delete(buf);
> >          buf = NULL;
> >      }
> > @@ -5899,7 +5899,7 @@ rewrite_flow_encap_nsh(struct xlate_ctx *ctx,
> >
> >      if (md_type == NSH_M_TYPE1) {
> >          flow->nsh.mdtype = NSH_M_TYPE1;
> > -        memset(flow->nsh.c, 0, sizeof flow->nsh.c);
> > +        memset(flow->nsh.context, 0, sizeof flow->nsh.context);
> >          if (buf) {
> >              /* Drop any MD2 context TLVs. */
> >              ofpbuf_delete(buf);
> > @@ -5930,7 +5930,7 @@ xlate_generic_encap_action(struct xlate_ctx
> > *ctx,
> >              rewrite_flow_encap_ethernet(ctx, flow, wc);
> >              break;
> >          case PT_NSH:
> > -            encap_data = rewrite_flow_encap_nsh(ctx, encap, flow, wc);
> > +            encap_data = rewrite_flow_push_nsh(ctx, encap, flow, wc);
> >              break;
> >          default:
> >              /* New packet type was checked during decoding. */
> > @@ -5973,7 +5973,7 @@ xlate_generic_decap_action(struct xlate_ctx
> > *ctx,
> >              }
> >              return false;
> >          case PT_NSH:
> > -            /* The decap_nsh action is generated at the commit executed as
> > +            /* The pop_nsh action is generated at the commit executed as
> >               * part of freezing the ctx for recirculation. Here we just set
> >               * the new packet type based on the NSH next protocol field. */
> >              switch (flow->nsh.np) {
> > diff --git a/tests/nsh.at b/tests/nsh.at
> > index aa80a2a..562f3da 100644
> > --- a/tests/nsh.at
> > +++ b/tests/nsh.at
> > @@ -105,7 +105,7 @@ bridge("br0")
> >
> >  Final flow:
> > in_port=1,vlan_tci=0x0000,dl_src=00:00:00:00:00:00,dl_dst=11:22:33:44:55:
> > 66,dl_type=0x894f,nsh_flags=0,nsh_mdtype=1,nsh_np=3,nsh_spi=0x1234,ns
> > h_si=255,nsh_c1=0x11223344,nsh_c2=0x0,nsh_c3=0x0,nsh_c4=0x0,nw_pro
> > to=0,nw_tos=0,nw_ecn=0,nw_ttl=0
> >  Megaflow:
> > recirc_id=0,eth,ip,in_port=1,dl_dst=66:77:88:99:aa:bb,nw_frag=no
> > -Datapath actions:
> > encap_nsh(flags=0,mdtype=1,np=3,spi=0x1234,si=255,c1=0x11223344,c2=0
> > x0,c3=0x0,c4=0x0),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),
> > pop_eth,decap_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x1)
> > +Datapath actions:
> > push_nsh(flags=0,mdtype=1,np=3,spi=0x1234,si=255,c1=0x11223344,c2=0x
> > 0,c3=0x0,c4=0x0),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),p
> > op_eth,pop_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x1)
> >  ])
> >
> >  AT_CHECK([
> > @@ -121,7 +121,7 @@ bridge("br0")
> >
> >  Final flow: unchanged
> >  Megaflow:
> > recirc_id=0,eth,in_port=4,dl_type=0x894f,nsh_mdtype=1,nsh_np=3,nsh_spi
> > =0x1234,nsh_c1=0x11223344
> > -Datapath actions: pop_eth,decap_nsh(),recirc(0x2)
> > +Datapath actions: pop_eth,pop_nsh(),recirc(0x2)
> >  ])
> >
> >  # Now send two real ICMP echo request packets in on port p1
> > @@ -139,7 +139,7 @@ ovs-appctl time/warp 1000
> >  AT_CHECK([
> >      ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v
> > ipv6 | sort
> >  ], [0], [flow-dump from non-dpdk interfaces:
> > -
> > recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(dst=1e:2c:e9:2a:66:9e),e
> > th_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s,
> > actions:encap_nsh(flags=0,mdtype=1,np=3,spi=0x1234,si=255,c1=0x112233
> > 44,c2=0x0,c3=0x0,c4=0x0),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44
> > :55:66),pop_eth,decap_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x3)
> > +recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(dst=1e:2c:e9:2a:66:9e),
> > eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s,
> > actions:push_nsh(flags=0,mdtype=1,np=3,spi=0x1234,si=255,c1=0x1122334
> > 4,c2=0x0,c3=0x0,c4=0x0),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:
> > 55:66),pop_eth,pop_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x3)
> >
> > recirc_id(0x3),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(fra
> > g=no), packets:1, bytes:98, used:0.0s, actions:2
> >  ])
> >
> > @@ -170,7 +170,7 @@ ovs-appctl time/warp 1000
> >  AT_CHECK([
> >      ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v
> > ipv6 | sort
> >  ], [0], [flow-dump from non-dpdk interfaces:
> > -
> > recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=
> > no), packets:1, bytes:98, used:0.0s,
> > actions:push_vlan(vid=100,pcp=0),encap_nsh(flags=0,mdtype=1,np=3,spi=0
> > x0,si=255,c1=0x0,c2=0x0,c3=0x0,c4=0x0),decap_nsh(),recirc(0x4)
> > +recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag
> > =no), packets:1, bytes:98, used:0.0s,
> > actions:push_vlan(vid=100,pcp=0),push_nsh(flags=0,mdtype=1,np=3,spi=0x
> > 0,si=255,c1=0x0,c2=0x0,c3=0x0,c4=0x0),pop_nsh(),recirc(0x4)
> >
> > recirc_id(0x4),in_port(1),packet_type(ns=0,id=0),eth_type(0x8100),vlan(vid
> > =100,pcp=0),encap(eth_type(0x0800),ipv4(frag=no)), packets:1, bytes:102,
> > used:0.0s, actions:2
> >  ])
> >
> > @@ -195,7 +195,7 @@ ovs-vsctl set bridge br0 datapath_type=dummy \
> >          add-port br0 v4 -- set Interface v4 type=patch options:peer=v3
> > ofport_request=4])
> >
> >  AT_DATA([flows.txt], [dnl
> > -
> > table=0,in_port=1,ip,actions=encap(nsh(md_type=2,tlv(0x1000,10,0x12345
> > 678))),set_field:0x1234-
> > >nsh_spi,encap(ethernet),set_field:11:22:33:44:55:66->dl_dst,3
> > +
> > table=0,in_port=1,ip,actions=encap(nsh(md_type=2,tlv(0x1000,10,0x12345
> > 678),tlv(0x2000,20,0xfedcba9876543210))),set_field:0x1234-
> > >nsh_spi,encap(ethernet),set_field:11:22:33:44:55:66->dl_dst,3
> >
> > table=0,in_port=4,dl_type=0x894f,nsh_mdtype=2,nsh_spi=0x1234,actions=
> > decap(),decap(),2
> >  ])
> >
> > @@ -205,7 +205,7 @@ AT_CHECK([
> >      ovs-ofctl -Oopenflow13 dump-flows br0 | ofctl_strip | sort | grep
> > actions
> >  ], [0], [dnl
> >   in_port=4,dl_type=0x894f,nsh_mdtype=2,nsh_spi=0x1234
> > actions=decap(),decap(),output:2
> > - ip,in_port=1
> > actions=encap(nsh(md_type=2,tlv(0x1000,10,0x12345678))),set_field:0x123
> > 4->nsh_spi,encap(ethernet),set_field:11:22:33:44:55:66->eth_dst,output:3
> > + ip,in_port=1
> > actions=encap(nsh(md_type=2,tlv(0x1000,10,0x12345678),tlv(0x2000,20,0x
> > fedcba9876543210))),set_field:0x1234-
> > >nsh_spi,encap(ethernet),set_field:11:22:33:44:55:66->eth_dst,output:3
> >  ])
> >
> >  AT_CHECK([
> > @@ -216,7 +216,7 @@ Flow:
> > icmp,in_port=1,vlan_tci=0x0000,dl_src=00:11:22:33:44:55,dl_dst=66:77:88:
> > 99
> >  bridge("br0")
> >  -------------
> >   0. ip,in_port=1, priority 32768
> > -    encap(nsh(md_type=2,tlv(0x1000,10,0x12345678)))
> > +
> > encap(nsh(md_type=2,tlv(0x1000,10,0x12345678),tlv(0x2000,20,0xfedcba9
> > 876543210)))
> >      set_field:0x1234->nsh_spi
> >      encap(ethernet)
> >      set_field:11:22:33:44:55:66->eth_dst
> > @@ -230,7 +230,7 @@ bridge("br0")
> >
> >  Final flow:
> > in_port=1,vlan_tci=0x0000,dl_src=00:00:00:00:00:00,dl_dst=11:22:33:44:55:
> > 66,dl_type=0x894f,nsh_flags=0,nsh_mdtype=2,nsh_np=3,nsh_spi=0x1234,ns
> > h_si=255,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0
> >  Megaflow:
> > recirc_id=0,eth,ip,in_port=1,dl_dst=66:77:88:99:aa:bb,nw_frag=no
> > -Datapath actions:
> > encap_nsh(flags=0,mdtype=2,np=3,spi=0x1234,si=255,md2=0x10000a0412
> > 345678),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),pop_eth,d
> > ecap_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x1)
> > +Datapath actions:
> > push_nsh(flags=0,mdtype=2,np=3,spi=0x1234,si=255,md2=0x10000a04123
> > 4567820001408fedcba9876543210),push_eth(src=00:00:00:00:00:00,dst=11
> > :22:33:44:55:66),pop_eth,pop_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(
> > 0x1)
> >  ])
> >
> >  AT_CHECK([
> > @@ -246,7 +246,7 @@ bridge("br0")
> >
> >  Final flow: unchanged
> >  Megaflow:
> > recirc_id=0,eth,in_port=4,dl_type=0x894f,nsh_mdtype=2,nsh_np=3,nsh_spi
> > =0x1234
> > -Datapath actions: pop_eth,decap_nsh(),recirc(0x2)
> > +Datapath actions: pop_eth,pop_nsh(),recirc(0x2)
> >  ])
> >
> >  # Now send two real ICMP echo request packets in on port p1
> > @@ -264,7 +264,7 @@ ovs-appctl time/warp 1000
> >  AT_CHECK([
> >      ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v
> > ipv6 | sort
> >  ], [0], [flow-dump from non-dpdk interfaces:
> > -
> > recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(dst=1e:2c:e9:2a:66:9e),e
> > th_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s,
> > actions:encap_nsh(flags=0,mdtype=2,np=3,spi=0x1234,si=255,md2=0x1000
> > 0a0412345678),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),po
> > p_eth,decap_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x3)
> > +recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(dst=1e:2c:e9:2a:66:9e),
> > eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s,
> > actions:push_nsh(flags=0,mdtype=2,np=3,spi=0x1234,si=255,md2=0x10000
> > a041234567820001408fedcba9876543210),push_eth(src=00:00:00:00:00:0
> > 0,dst=11:22:33:44:55:66),pop_eth,pop_nsh(),set(eth(dst=11:22:33:44:55:66)
> > ),recirc(0x3)
> >
> > recirc_id(0x3),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(fra
> > g=no), packets:1, bytes:98, used:0.0s, actions:2
> >  ])
> >
> > @@ -577,8 +577,8 @@ ovs-appctl time/warp 1000
> >  AT_CHECK([
> >      ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v
> > ipv6 | sort
> >  ], [0], [flow-dump from non-dpdk interfaces:
> > -
> > recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=1
> > 92.168.10.30,frag=no), packets:1, bytes:98, used:0.0s,
> > actions:pop_eth,encap_nsh(flags=0,mdtype=1,np=1,spi=0x3000,si=255,c1=0
> > x0,c2=0x0,c3=0x0,c4=0x0),clone(tnl_push(tnl_port(4789),header(size=50,ty
> > pe=4,eth(dst=aa:55:00:00:00:03,src=aa:55:00:00:00:01,dl_type=0x0800),ipv
> > 4(src=10.0.0.1,dst=10.0.0.3,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,d
> > st=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(1)),set(ipv4(s
> > rc=30.0.0.1,dst=30.0.0.3)),tnl_pop(4789))
> > -tunnel(tun_id=0x0,src=30.0.0.1,dst=30.0.0.3,flags(-df-
> > csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(np
> > =1,spi=0x3000,si=255), packets:1, bytes:108, used:0.0s,
> > actions:decap_nsh(),recirc(0x1)
> > +recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=
> > 192.168.10.30,frag=no), packets:1, bytes:98, used:0.0s,
> > actions:pop_eth,push_nsh(flags=0,mdtype=1,np=1,spi=0x3000,si=255,c1=0x
> > 0,c2=0x0,c3=0x0,c4=0x0),clone(tnl_push(tnl_port(4789),header(size=50,typ
> > e=4,eth(dst=aa:55:00:00:00:03,src=aa:55:00:00:00:01,dl_type=0x0800),ipv4
> > (src=10.0.0.1,dst=10.0.0.3,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,ds
> > t=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(1)),set(ipv4(sr
> > c=30.0.0.1,dst=30.0.0.3)),tnl_pop(4789))
> > +tunnel(tun_id=0x0,src=30.0.0.1,dst=30.0.0.3,flags(-df-
> > csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(np
> > =1,spi=0x3000,si=255), packets:1, bytes:108, used:0.0s,
> > actions:pop_nsh(),recirc(0x1)
> >  tunnel(tun_id=0x0,src=30.0.0.1,dst=30.0.0.3,flags(-df-
> > csum+key)),recirc_id(0x1),in_port(4789),packet_type(ns=1,id=0x800),ipv4(f
> > rag=no), packets:1, bytes:84, used:0.0s,
> > actions:push_eth(src=00:00:00:00:00:00,dst=aa:55:aa:55:00:03),6
> >  ])
> >
> > @@ -631,9 +631,9 @@ ovs-appctl time/warp 1000
> >  AT_CHECK([
> >      ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v
> > ipv6 | sort
> >  ], [0], [flow-dump from non-dpdk interfaces:
> > -
> > recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=1
> > 92.168.10.20/255.255.255.248,frag=no), packets:1, bytes:98, used:0.0s,
> > actions:pop_eth,encap_nsh(flags=0,mdtype=1,np=1,spi=0x3020,si=255,c1=0
> > x0,c2=0x0,c3=0x0,c4=0x0),clone(tnl_push(tnl_port(4789),header(size=50,ty
> > pe=4,eth(dst=aa:55:00:00:00:02,src=aa:55:00:00:00:01,dl_type=0x0800),ipv
> > 4(src=10.0.0.1,dst=10.0.0.2,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,d
> > st=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(1)),set(ipv4(s
> > rc=20.0.0.1,dst=20.0.0.2)),tnl_pop(4789))
> > +recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=
> > 192.168.10.20/255.255.255.248,frag=no), packets:1, bytes:98, used:0.0s,
> > actions:pop_eth,push_nsh(flags=0,mdtype=1,np=1,spi=0x3020,si=255,c1=0x
> > 0,c2=0x0,c3=0x0,c4=0x0),clone(tnl_push(tnl_port(4789),header(size=50,typ
> > e=4,eth(dst=aa:55:00:00:00:02,src=aa:55:00:00:00:01,dl_type=0x0800),ipv4
> > (src=10.0.0.1,dst=10.0.0.2,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,ds
> > t=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(1)),set(ipv4(sr
> > c=20.0.0.1,dst=20.0.0.2)),tnl_pop(4789))
> >  tunnel(tun_id=0x0,src=20.0.0.1,dst=20.0.0.2,flags(-df-
> > csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(spi
> > =0x3020,si=255), packets:1, bytes:108, used:0.0s,
> > actions:push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),set(nsh(spi=
> > 0x3020,si=254)),pop_eth,clone(tnl_push(tnl_port(4789),header(size=50,typ
> > e=4,eth(dst=aa:55:00:00:00:03,src=aa:55:00:00:00:02,dl_type=0x0800),ipv4
> > (src=20.0.0.2,dst=20.0.0.3,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,ds
> > t=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(2)),set(ipv4(sr
> > c=30.0.0.2,dst=30.0.0.3)),tnl_pop(4789))
> > -tunnel(tun_id=0x0,src=30.0.0.2,dst=30.0.0.3,flags(-df-
> > csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(np
> > =1,spi=0x3020,si=254), packets:1, bytes:108, used:0.0s,
> > actions:decap_nsh(),recirc(0x2)
> > +tunnel(tun_id=0x0,src=30.0.0.2,dst=30.0.0.3,flags(-df-
> > csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(np
> > =1,spi=0x3020,si=254), packets:1, bytes:108, used:0.0s,
> > actions:pop_nsh(),recirc(0x2)
> >  tunnel(tun_id=0x0,src=30.0.0.2,dst=30.0.0.3,flags(-df-
> > csum+key)),recirc_id(0x2),in_port(4789),packet_type(ns=1,id=0x800),ipv4(f
> > rag=no), packets:1, bytes:84, used:0.0s,
> > actions:push_eth(src=00:00:00:00:00:00,dst=aa:55:aa:55:00:03),6
> >  ])
> >
> > --
> > 2.1.0
>  
>
diff mbox

Patch

diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h
index bc6c94b..d7f9029 100644
--- a/datapath/linux/compat/include/linux/openvswitch.h
+++ b/datapath/linux/compat/include/linux/openvswitch.h
@@ -369,7 +369,7 @@  enum ovs_key_attr {
 #ifndef __KERNEL__
 	/* Only used within userspace data path. */
 	OVS_KEY_ATTR_PACKET_TYPE,  /* be32 packet type */
-	OVS_KEY_ATTR_NSH,	   /* struct ovs_key_nsh */
+	OVS_KEY_ATTR_NSH,	   /* Nested set of ovs_nsh_key_* */
 #endif
 
 	__OVS_KEY_ATTR_MAX
@@ -492,13 +492,27 @@  struct ovs_key_ct_labels {
 	};
 };
 
-struct ovs_key_nsh {
-    __u8 flags;
-    __u8 mdtype;
-    __u8 np;
-    __u8 pad;
-    __be32 path_hdr;
-    __be32 c[4];
+enum ovs_nsh_key_attr {
+	OVS_NSH_KEY_ATTR_BASE,          /* struct ovs_nsh_key_base. */
+	OVS_NSH_KEY_ATTR_MD1,           /* struct ovs_nsh_key_md1. */
+	OVS_NSH_KEY_ATTR_MD2,           /* variable-length octets. */
+	__OVS_NSH_KEY_ATTR_MAX
+};
+
+#define OVS_NSH_KEY_ATTR_MAX (__OVS_NSH_KEY_ATTR_MAX - 1)
+
+struct ovs_nsh_key_base {
+	__u8 flags;
+	__u8 mdtype;
+	__u8 np;
+	__u8 pad;
+	__be32 path_hdr;
+};
+
+#define NSH_MD1_CONTEXT_SIZE 4
+
+struct ovs_nsh_key_md1 {
+	__be32 context[NSH_MD1_CONTEXT_SIZE];
 };
 
 /* OVS_KEY_ATTR_CT_STATE flags */
@@ -793,24 +807,7 @@  struct ovs_action_push_eth {
 	struct ovs_key_ethernet addresses;
 };
 
-#define OVS_ENCAP_NSH_MAX_MD_LEN 16
-/*
- * struct ovs_action_encap_nsh - %OVS_ACTION_ATTR_ENCAP_NSH
- * @flags: NSH header flags.
- * @mdtype: NSH metadata type.
- * @mdlen: Length of NSH metadata in bytes.
- * @np: NSH next_protocol: Inner packet type.
- * @path_hdr: NSH service path id and service index.
- * @metadata: NSH metadata for MD type 1 or 2
- */
-struct ovs_action_encap_nsh {
-    uint8_t flags;
-    uint8_t mdtype;
-    uint8_t mdlen;
-    uint8_t np;
-    __be32 path_hdr;
-    uint8_t metadata[OVS_ENCAP_NSH_MAX_MD_LEN];
-};
+#define OVS_PUSH_NSH_MAX_MD_LEN 248
 
 /**
  * enum ovs_nat_attr - Attributes for %OVS_CT_ATTR_NAT.
@@ -887,8 +884,8 @@  enum ovs_nat_attr {
  * @OVS_ACTION_ATTR_PUSH_ETH: Push a new outermost Ethernet header onto the
  * packet.
  * @OVS_ACTION_ATTR_POP_ETH: Pop the outermost Ethernet header off the packet.
- * @OVS_ACTION_ATTR_ENCAP_NSH: encap NSH action to push NSH header.
- * @OVS_ACTION_ATTR_DECAP_NSH: decap NSH action to remove NSH header.
+ * @OVS_ACTION_ATTR_PUSH_NSH: push NSH header to the packet.
+ * @OVS_ACTION_ATTR_POP_NSH: pop the outermost NSH header off the packet.
  *
  * Only a single header can be set with a single %OVS_ACTION_ATTR_SET.  Not all
  * fields within a header are modifiable, e.g. the IPv4 protocol and fragment
@@ -930,8 +927,8 @@  enum ovs_action_attr {
 	OVS_ACTION_ATTR_TUNNEL_POP,    /* u32 port number. */
 	OVS_ACTION_ATTR_CLONE,         /* Nested OVS_CLONE_ATTR_*.  */
 	OVS_ACTION_ATTR_METER,         /* u32 meter number. */
-	OVS_ACTION_ATTR_ENCAP_NSH,    /* struct ovs_action_encap_nsh. */
-	OVS_ACTION_ATTR_DECAP_NSH,    /* No argument. */
+	OVS_ACTION_ATTR_PUSH_NSH,      /* Nested OVS_NSH_KEY_ATTR_*. */
+	OVS_ACTION_ATTR_POP_NSH,       /* No argument. */
 #endif
 	__OVS_ACTION_ATTR_MAX,	      /* Nothing past this will be accepted
 				       * from userspace. */
diff --git a/include/openvswitch/nsh.h b/include/openvswitch/nsh.h
index f4ccadc..ed9054e 100644
--- a/include/openvswitch/nsh.h
+++ b/include/openvswitch/nsh.h
@@ -51,7 +51,7 @@  extern "C" {
  * @nshc<1-4>: NSH Contexts.
  */
 struct nsh_md1_ctx {
-    ovs_16aligned_be32 c[4];
+    ovs_16aligned_be32 context[4];
 };
 
 struct nsh_md2_tlv {
@@ -72,6 +72,8 @@  struct nsh_hdr {
     };
 };
 
+#define NSH_M_TYPE2_MAX_LEN 256
+
 /* Masking NSH header fields. */
 #define NSH_VER_MASK       0xc000
 #define NSH_VER_SHIFT      14
@@ -110,10 +112,17 @@  struct nsh_hdr {
 /* NSH MD Type 1 header Length. */
 #define NSH_M_TYPE1_LEN   24
 
+/* NSH MD Type 2 header maximum Length. */
+#define NSH_M_TYPE2_MAX_LEN 256
+
+#define NSH_MD1_CTX(nsh_hdr_ptr) (&(nsh_hdr_ptr)->md1)
+
+#define NSH_MD2_CTX(nsh_hdr_ptr) (&(nsh_hdr_ptr)->md2)
+
 static inline uint16_t
 nsh_hdr_len(const struct nsh_hdr *nsh)
 {
-    return 4 * (ntohs(nsh->ver_flags_len) & NSH_LEN_MASK) >> NSH_LEN_SHIFT;
+    return ((ntohs(nsh->ver_flags_len) & NSH_LEN_MASK) >> NSH_LEN_SHIFT) << 2;
 }
 
 static inline struct nsh_md1_ctx *
@@ -128,6 +137,24 @@  nsh_md2_ctx(struct nsh_hdr *nsh)
     return &nsh->md2;
 }
 
+static inline uint8_t
+nsh_get_ver(const struct nsh_hdr *nsh)
+{
+    return (ntohs(nsh->ver_flags_len) & NSH_VER_MASK) >> NSH_VER_SHIFT;
+}
+
+static inline uint8_t
+nsh_get_len(const struct nsh_hdr *nsh)
+{
+    return (ntohs(nsh->ver_flags_len) & NSH_LEN_MASK) >> NSH_LEN_SHIFT;
+}
+
+static inline uint8_t
+nsh_get_flags(const struct nsh_hdr *nsh)
+{
+    return (ntohs(nsh->ver_flags_len) & NSH_FLAGS_MASK) >> NSH_FLAGS_SHIFT;
+}
+
 #ifdef  __cplusplus
 }
 #endif
diff --git a/include/openvswitch/packets.h b/include/openvswitch/packets.h
index be91e02..5ee3099 100644
--- a/include/openvswitch/packets.h
+++ b/include/openvswitch/packets.h
@@ -84,7 +84,16 @@  struct flow_nsh {
     uint8_t np;
     uint8_t si;
     ovs_be32 spi;
-    ovs_be32 c[4];
+    ovs_be32 context[4];
+};
+
+struct ovs_key_nsh {
+    uint8_t flags;
+    uint8_t mdtype;
+    uint8_t np;
+    uint8_t pad;
+    ovs_be32 path_hdr;
+    ovs_be32 context[4];
 };
 
 /* NSH flags */
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index e2cd931..527fa0e 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -5407,8 +5407,8 @@  dp_execute_cb(void *aux_, struct dp_packet_batch *packets_,
     case OVS_ACTION_ATTR_PUSH_ETH:
     case OVS_ACTION_ATTR_POP_ETH:
     case OVS_ACTION_ATTR_CLONE:
-    case OVS_ACTION_ATTR_ENCAP_NSH:
-    case OVS_ACTION_ATTR_DECAP_NSH:
+    case OVS_ACTION_ATTR_PUSH_NSH:
+    case OVS_ACTION_ATTR_POP_NSH:
     case __OVS_ACTION_ATTR_MAX:
         OVS_NOT_REACHED();
     }
diff --git a/lib/dpif.c b/lib/dpif.c
index 4c5eac6..2e6cd17 100644
--- a/lib/dpif.c
+++ b/lib/dpif.c
@@ -1263,8 +1263,8 @@  dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_,
     case OVS_ACTION_ATTR_PUSH_ETH:
     case OVS_ACTION_ATTR_POP_ETH:
     case OVS_ACTION_ATTR_CLONE:
-    case OVS_ACTION_ATTR_ENCAP_NSH:
-    case OVS_ACTION_ATTR_DECAP_NSH:
+    case OVS_ACTION_ATTR_PUSH_NSH:
+    case OVS_ACTION_ATTR_POP_NSH:
     case OVS_ACTION_ATTR_UNSPEC:
     case __OVS_ACTION_ATTR_MAX:
         OVS_NOT_REACHED();
diff --git a/lib/flow.c b/lib/flow.c
index b2b10aa..f89dc8d 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -533,38 +533,40 @@  bool
 parse_nsh(const void **datap, size_t *sizep, struct flow_nsh *key)
 {
     const struct nsh_hdr *nsh = (const struct nsh_hdr *) *datap;
-    uint16_t ver_flags_len;
     uint8_t version, length, flags;
     uint32_t path_hdr;
 
-    /* Check if it is long enough for NSH header, doesn't support
-     * MD type 2 yet
-     */
-    if (OVS_UNLIKELY(*sizep < NSH_M_TYPE1_LEN)) {
+    if (OVS_UNLIKELY(*sizep < NSH_BASE_HDR_LEN)) {
         return false;
     }
 
     memset(key, 0, sizeof(struct flow_nsh));
 
-    ver_flags_len = ntohs(nsh->ver_flags_len);
-    version = (ver_flags_len & NSH_VER_MASK) >> NSH_VER_SHIFT;
-    flags = (ver_flags_len & NSH_FLAGS_MASK) >> NSH_FLAGS_SHIFT;
+    version = nsh_get_ver(nsh);
+    flags = nsh_get_flags(nsh);
 
-    /* NSH header length is in 4 byte words. */
-    length = ((ver_flags_len & NSH_LEN_MASK) >> NSH_LEN_SHIFT) << 2;
+    length = nsh_hdr_len(nsh);
 
     if (version != 0) {
         return false;
     }
 
-    if (length != NSH_M_TYPE1_LEN) {
-        return false;
-    }
-
     key->flags = flags;
     key->mdtype = nsh->md_type;
     key->np = nsh->next_proto;
 
+    if (nsh->md_type == NSH_M_TYPE1 && length != NSH_M_TYPE1_LEN) {
+        return false;
+    }
+
+    if (nsh->md_type == NSH_M_TYPE2 && length < NSH_BASE_HDR_LEN) {
+        return false;
+    }
+
+    if (OVS_UNLIKELY(*sizep < length)) {
+        return false;
+    }
+
     path_hdr = ntohl(get_16aligned_be32(&nsh->path_hdr));
     key->si = (path_hdr & NSH_SI_MASK) >> NSH_SI_SHIFT;
     key->spi = htonl((path_hdr & NSH_SPI_MASK) >> NSH_SPI_SHIFT);
@@ -572,11 +574,12 @@  parse_nsh(const void **datap, size_t *sizep, struct flow_nsh *key)
     switch (key->mdtype) {
         case NSH_M_TYPE1:
             for (size_t i = 0; i < 4; i++) {
-                key->c[i] = get_16aligned_be32(&nsh->md1.c[i]);
+                key->context[i] = get_16aligned_be32(&nsh->md1.context[i]);
             }
             break;
         case NSH_M_TYPE2:
-            /* Don't support MD type 2 yet, so return false */
+            /* Don't support MD type 2 metedata parsing yet */
+            break;
         default:
             return false;
     }
@@ -885,9 +888,7 @@  miniflow_extract(struct dp_packet *packet, struct miniflow *dst)
                                         sizeof(uint64_t));
                 }
                 else if (nsh.mdtype == NSH_M_TYPE2) {
-                    /* parse_nsh has stopped it from arriving here for
-                     * MD type 2, will add MD type 2 support code here later
-                     */
+                    /* Can't parse MD type 2 metedata yet */
                 }
             }
         }
@@ -1692,7 +1693,7 @@  flow_wildcards_init_for_packet(struct flow_wildcards *wc,
         WC_MASK_FIELD(wc, nsh.np);
         WC_MASK_FIELD(wc, nsh.spi);
         WC_MASK_FIELD(wc, nsh.si);
-        WC_MASK_FIELD(wc, nsh.c);
+        WC_MASK_FIELD(wc, nsh.context);
     } else {
         return; /* Unknown ethertype. */
     }
@@ -1826,7 +1827,7 @@  flow_wc_map(const struct flow *flow, struct flowmap *map)
         FLOWMAP_SET(map, nsh.np);
         FLOWMAP_SET(map, nsh.spi);
         FLOWMAP_SET(map, nsh.si);
-        FLOWMAP_SET(map, nsh.c);
+        FLOWMAP_SET(map, nsh.context);
     }
 }
 
diff --git a/lib/match.c b/lib/match.c
index 36c78eb..8952c99 100644
--- a/lib/match.c
+++ b/lib/match.c
@@ -1266,10 +1266,14 @@  format_nsh_masked(struct ds *s, const struct flow *f, const struct flow *m)
     format_be32_masked_hex(s, "nsh_spi", f->nsh.spi, m->nsh.spi);
     format_uint8_masked(s, "nsh_si", f->nsh.si, m->nsh.si);
     if (m->nsh.mdtype == UINT8_MAX && f->nsh.mdtype == NSH_M_TYPE1) {
-        format_be32_masked_hex(s, "nsh_c1", f->nsh.c[0], m->nsh.c[0]);
-        format_be32_masked_hex(s, "nsh_c2", f->nsh.c[1], m->nsh.c[1]);
-        format_be32_masked_hex(s, "nsh_c3", f->nsh.c[2], m->nsh.c[2]);
-        format_be32_masked_hex(s, "nsh_c4", f->nsh.c[3], m->nsh.c[3]);
+        format_be32_masked_hex(s, "nsh_c1", f->nsh.context[0],
+                               m->nsh.context[0]);
+        format_be32_masked_hex(s, "nsh_c2", f->nsh.context[1],
+                               m->nsh.context[1]);
+        format_be32_masked_hex(s, "nsh_c3", f->nsh.context[2],
+                               m->nsh.context[2]);
+        format_be32_masked_hex(s, "nsh_c4", f->nsh.context[3],
+                               m->nsh.context[3]);
     }
 }
 
diff --git a/lib/meta-flow.c b/lib/meta-flow.c
index 64a8cf1..beeddf1 100644
--- a/lib/meta-flow.c
+++ b/lib/meta-flow.c
@@ -373,7 +373,7 @@  mf_is_all_wild(const struct mf_field *mf, const struct flow_wildcards *wc)
     case MFF_NSH_C2:
     case MFF_NSH_C3:
     case MFF_NSH_C4:
-        return !wc->masks.nsh.c[mf->id - MFF_NSH_C1];
+        return !wc->masks.nsh.context[mf->id - MFF_NSH_C1];
 
     case MFF_N_IDS:
     default:
@@ -915,7 +915,7 @@  mf_get_value(const struct mf_field *mf, const struct flow *flow,
     case MFF_NSH_C2:
     case MFF_NSH_C3:
     case MFF_NSH_C4:
-        value->be32 = flow->nsh.c[mf->id - MFF_NSH_C1];
+        value->be32 = flow->nsh.context[mf->id - MFF_NSH_C1];
         break;
 
     case MFF_N_IDS:
@@ -1230,7 +1230,8 @@  mf_set_value(const struct mf_field *mf,
     case MFF_NSH_C2:
     case MFF_NSH_C3:
     case MFF_NSH_C4:
-        MATCH_SET_FIELD_BE32(match, nsh.c[mf->id - MFF_NSH_C1], value->be32);
+        MATCH_SET_FIELD_BE32(match, nsh.context[mf->id - MFF_NSH_C1],
+                             value->be32);
         break;
 
     case MFF_N_IDS:
@@ -1621,7 +1622,7 @@  mf_set_flow_value(const struct mf_field *mf,
     case MFF_NSH_C2:
     case MFF_NSH_C3:
     case MFF_NSH_C4:
-        flow->nsh.c[mf->id - MFF_NSH_C1] = value->be32;
+        flow->nsh.context[mf->id - MFF_NSH_C1] = value->be32;
         break;
 
     case MFF_N_IDS:
@@ -2112,7 +2113,7 @@  mf_set_wild(const struct mf_field *mf, struct match *match, char **err_str)
     case MFF_NSH_C2:
     case MFF_NSH_C3:
     case MFF_NSH_C4:
-        MATCH_SET_FIELD_MASKED(match, nsh.c[mf->id - MFF_NSH_C1],
+        MATCH_SET_FIELD_MASKED(match, nsh.context[mf->id - MFF_NSH_C1],
                                htonl(0), htonl(0));
         break;
 
@@ -2372,7 +2373,7 @@  mf_set(const struct mf_field *mf,
     case MFF_NSH_C2:
     case MFF_NSH_C3:
     case MFF_NSH_C4:
-        MATCH_SET_FIELD_MASKED(match, nsh.c[mf->id - MFF_NSH_C1],
+        MATCH_SET_FIELD_MASKED(match, nsh.context[mf->id - MFF_NSH_C1],
                                value->be32, mask->be32);
         break;
 
diff --git a/lib/nx-match.c b/lib/nx-match.c
index b782e8c..8f2a442 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -1165,8 +1165,8 @@  nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, const struct match *match,
                 match->wc.masks.nsh.spi);
     nxm_put_8m(&ctx, MFF_NSH_SI, oxm, flow->nsh.si, match->wc.masks.nsh.si);
     for (int i = 0; i < 4; i++) {
-        nxm_put_32m(&ctx, MFF_NSH_C1 + i, oxm, flow->nsh.c[i],
-                    match->wc.masks.nsh.c[i]);
+        nxm_put_32m(&ctx, MFF_NSH_C1 + i, oxm, flow->nsh.context[i],
+                    match->wc.masks.nsh.context[i]);
     }
 
     /* Registers. */
diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index 5f4d23a..0a9a535 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -273,19 +273,22 @@  odp_set_nd(struct dp_packet *packet, const struct ovs_key_nd *key,
 /* Set the NSH header. Assumes the NSH header is present and matches the
  * MD format of the key. The slow path must take case of that. */
 static void
-odp_set_nsh(struct dp_packet *packet, const struct ovs_key_nsh *key,
-            const struct ovs_key_nsh *mask)
+odp_set_nsh(struct dp_packet *packet, const struct flow_nsh *key,
+            const struct flow_nsh *mask)
 {
     struct nsh_hdr *nsh = dp_packet_l3(packet);
+    ovs_be32 path_hdr;
 
     if (!mask) {
         nsh->ver_flags_len = htons(key->flags << NSH_FLAGS_SHIFT) |
                              (nsh->ver_flags_len & ~htons(NSH_FLAGS_MASK));
-        put_16aligned_be32(&nsh->path_hdr, key->path_hdr);
+        path_hdr = htonl((ntohl(key->spi) << NSH_SPI_SHIFT) |
+                         key->si);
+        put_16aligned_be32(&nsh->path_hdr, path_hdr);
         switch (nsh->md_type) {
             case NSH_M_TYPE1:
                 for (int i = 0; i < 4; i++) {
-                    put_16aligned_be32(&nsh->md1.c[i], key->c[i]);
+                    put_16aligned_be32(&nsh->md1.context[i], key->context[i]);
                 }
                 break;
             case NSH_M_TYPE2:
@@ -300,16 +303,24 @@  odp_set_nsh(struct dp_packet *packet, const struct ovs_key_nsh *key,
         nsh->ver_flags_len = htons(flags << NSH_FLAGS_SHIFT) |
                              (nsh->ver_flags_len & ~htons(NSH_FLAGS_MASK));
 
-        ovs_be32 path_hdr = get_16aligned_be32(&nsh->path_hdr);
-        path_hdr = key->path_hdr | (path_hdr & ~mask->path_hdr);
+        path_hdr = get_16aligned_be32(&nsh->path_hdr);
+        uint32_t spi = (ntohl(path_hdr) & NSH_SPI_MASK) >> NSH_SPI_SHIFT;
+        uint8_t si = (ntohl(path_hdr) & NSH_SI_MASK) >> NSH_SI_SHIFT;
+        uint32_t spi_mask = ntohl(mask->spi);
+        if (spi_mask == 0x00ffffff) {
+            spi_mask = UINT32_MAX;
+        }
+        spi = ntohl(key->spi) | (spi & ~spi_mask);
+        si = key->si | (si & ~mask->si);
+        path_hdr = htonl((spi << NSH_SPI_SHIFT) | si);
         put_16aligned_be32(&nsh->path_hdr, path_hdr);
         switch (nsh->md_type) {
             case NSH_M_TYPE1:
                 for (int i = 0; i < 4; i++) {
-                    ovs_be32 p = get_16aligned_be32(&nsh->md1.c[i]);
-                    ovs_be32 k = key->c[i];
-                    ovs_be32 m = mask->c[i];
-                    put_16aligned_be32(&nsh->md1.c[i], k | (p & ~m));
+                    ovs_be32 p = get_16aligned_be32(&nsh->md1.context[i]);
+                    ovs_be32 k = key->context[i];
+                    ovs_be32 m = mask->context[i];
+                    put_16aligned_be32(&nsh->md1.context[i], k | (p & ~m));
                 }
                 break;
             case NSH_M_TYPE2:
@@ -345,9 +356,12 @@  odp_execute_set_action(struct dp_packet *packet, const struct nlattr *a)
         odp_eth_set_addrs(packet, nl_attr_get(a), NULL);
         break;
 
-    case OVS_KEY_ATTR_NSH:
-        odp_set_nsh(packet, nl_attr_get(a), NULL);
+    case OVS_KEY_ATTR_NSH: {
+        struct flow_nsh nsh;
+        odp_nsh_key_from_attr(a, &nsh);
+        odp_set_nsh(packet, &nsh, NULL);
         break;
+    }
 
     case OVS_KEY_ATTR_IPV4:
         ipv4_key = nl_attr_get_unspec(a, sizeof(struct ovs_key_ipv4));
@@ -473,10 +487,24 @@  odp_execute_masked_set_action(struct dp_packet *packet,
                           get_mask(a, struct ovs_key_ethernet));
         break;
 
-    case OVS_KEY_ATTR_NSH:
-        odp_set_nsh(packet, nl_attr_get(a),
-                    get_mask(a, struct ovs_key_nsh));
+    case OVS_KEY_ATTR_NSH: {
+        struct flow_nsh nsh, nsh_mask;
+        size_t size = nl_attr_get_size(a) / 2;
+
+        struct nlattr attr[1 + size / sizeof(struct nlattr) + 1];
+        struct nlattr mask[1 + size / sizeof(struct nlattr) + 1];
+
+        mask->nla_type = attr->nla_type = nl_attr_type(a);
+        mask->nla_len = attr->nla_len = NLA_HDRLEN + size;
+        memcpy(attr + 1, (char *)(a + 1), size);
+        memcpy(mask + 1, (char *)(a + 1) + size, size);
+
+        odp_nsh_key_from_attr(attr, &nsh);
+        odp_nsh_key_from_attr(mask, &nsh_mask);
+        odp_set_nsh(packet, &nsh, &nsh_mask);
+
         break;
+    }
 
     case OVS_KEY_ATTR_IPV4:
         odp_set_ipv4(packet, nl_attr_get(a),
@@ -652,8 +680,8 @@  requires_datapath_assistance(const struct nlattr *a)
     case OVS_ACTION_ATTR_PUSH_ETH:
     case OVS_ACTION_ATTR_POP_ETH:
     case OVS_ACTION_ATTR_CLONE:
-    case OVS_ACTION_ATTR_ENCAP_NSH:
-    case OVS_ACTION_ATTR_DECAP_NSH:
+    case OVS_ACTION_ATTR_PUSH_NSH:
+    case OVS_ACTION_ATTR_POP_NSH:
         return false;
 
     case OVS_ACTION_ATTR_UNSPEC:
@@ -818,18 +846,21 @@  odp_execute_actions(void *dp, struct dp_packet_batch *batch, bool steal,
             }
             break;
 
-        case OVS_ACTION_ATTR_ENCAP_NSH: {
-            const struct ovs_action_encap_nsh *enc_nsh = nl_attr_get(a);
+        case OVS_ACTION_ATTR_PUSH_NSH: {
+            uint8_t buffer[256];
+            struct nsh_hdr *nsh_hdr = ALIGNED_CAST(struct nsh_hdr *, buffer);
+            const struct nsh_hdr *nsh_hdr_src = nsh_hdr;
+            odp_nsh_hdr_from_attr(nl_attr_get(a), nsh_hdr);
             DP_PACKET_BATCH_FOR_EACH (packet, batch) {
-                encap_nsh(packet, enc_nsh);
+                push_nsh(packet, nsh_hdr_src);
             }
             break;
         }
-        case OVS_ACTION_ATTR_DECAP_NSH: {
+        case OVS_ACTION_ATTR_POP_NSH: {
             size_t i, num = batch->count;
 
             DP_PACKET_BATCH_REFILL_FOR_EACH (i, num, packet, batch) {
-                if (decap_nsh(packet)) {
+                if (pop_nsh(packet)) {
                     dp_packet_batch_refill(batch, packet, i);
                 } else {
                     dp_packet_delete(packet);
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 4f1499e..3e4403a 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -129,8 +129,8 @@  odp_action_len(uint16_t type)
     case OVS_ACTION_ATTR_PUSH_ETH: return sizeof(struct ovs_action_push_eth);
     case OVS_ACTION_ATTR_POP_ETH: return 0;
     case OVS_ACTION_ATTR_CLONE: return ATTR_LEN_VARIABLE;
-    case OVS_ACTION_ATTR_ENCAP_NSH: return ATTR_LEN_VARIABLE;
-    case OVS_ACTION_ATTR_DECAP_NSH: return 0;
+    case OVS_ACTION_ATTR_PUSH_NSH: return ATTR_LEN_VARIABLE;
+    case OVS_ACTION_ATTR_POP_NSH: return 0;
 
     case OVS_ACTION_ATTR_UNSPEC:
     case __OVS_ACTION_ATTR_MAX:
@@ -264,7 +264,7 @@  format_nsh_key(struct ds *ds, const struct ovs_key_nsh *key)
     switch (key->mdtype) {
         case NSH_M_TYPE1:
             for (int i = 0; i < 4; i++) {
-                ds_put_format(ds, ",c%d=0x%x", i + 1, ntohl(key->c[i]));
+                ds_put_format(ds, ",c%d=0x%x", i + 1, ntohl(key->context[i]));
             }
             break;
         case NSH_M_TYPE2:
@@ -334,41 +334,50 @@  format_nsh_key_mask(struct ds *ds, const struct ovs_key_nsh *key,
         format_uint8_masked(ds, &first, "np", key->np, mask->np);
         format_be32_masked(ds, &first, "spi", htonl(spi), htonl(spi_mask));
         format_uint8_masked(ds, &first, "si", si, si_mask);
-        format_be32_masked(ds, &first, "c1", key->c[0], mask->c[0]);
-        format_be32_masked(ds, &first, "c2", key->c[1], mask->c[1]);
-        format_be32_masked(ds, &first, "c3", key->c[2], mask->c[2]);
-        format_be32_masked(ds, &first, "c4", key->c[3], mask->c[3]);
+        format_be32_masked(ds, &first, "c1", key->context[0],
+                           mask->context[0]);
+        format_be32_masked(ds, &first, "c2", key->context[1],
+                           mask->context[1]);
+        format_be32_masked(ds, &first, "c3", key->context[2],
+                           mask->context[2]);
+        format_be32_masked(ds, &first, "c4", key->context[3],
+                           mask->context[3]);
     }
 }
 
 static void
-format_odp_encap_nsh_action(struct ds *ds,
-                            const struct ovs_action_encap_nsh *encap_nsh)
+format_odp_push_nsh_action(struct ds *ds,
+                           const struct nsh_hdr *nsh_hdr)
  {
-    uint32_t path_hdr = ntohl(encap_nsh->path_hdr);
+    size_t mdlen = (((ntohs(nsh_hdr->ver_flags_len) & NSH_LEN_MASK)
+                         >> NSH_LEN_SHIFT) << 2) - NSH_BASE_HDR_LEN;
+    uint32_t path_hdr = ntohl(get_16aligned_be32(&nsh_hdr->path_hdr));
     uint32_t spi = (path_hdr & NSH_SPI_MASK) >> NSH_SPI_SHIFT;
     uint8_t si = (path_hdr & NSH_SI_MASK) >> NSH_SI_SHIFT;
+    uint8_t flags = (ntohs(nsh_hdr->ver_flags_len) & NSH_FLAGS_MASK)
+                        >> NSH_FLAGS_SHIFT;
 
-    ds_put_cstr(ds, "encap_nsh(");
-    ds_put_format(ds, "flags=%d", encap_nsh->flags);
-    ds_put_format(ds, ",mdtype=%d", encap_nsh->mdtype);
-    ds_put_format(ds, ",np=%d", encap_nsh->np);
+    ds_put_cstr(ds, "push_nsh(");
+    ds_put_format(ds, "flags=%d", flags);
+    ds_put_format(ds, ",mdtype=%d", nsh_hdr->md_type);
+    ds_put_format(ds, ",np=%d", nsh_hdr->next_proto);
     ds_put_format(ds, ",spi=0x%x", spi);
     ds_put_format(ds, ",si=%d", si);
-    switch (encap_nsh->mdtype) {
+    switch (nsh_hdr->md_type) {
     case NSH_M_TYPE1: {
-        struct nsh_md1_ctx *md1_ctx =
-            ALIGNED_CAST(struct nsh_md1_ctx *, encap_nsh->metadata);
+        const struct nsh_md1_ctx *md1_ctx = NSH_MD1_CTX(nsh_hdr);
         for (int i = 0; i < 4; i++) {
             ds_put_format(ds, ",c%d=0x%x", i + 1,
-                          ntohl(get_16aligned_be32(&md1_ctx->c[i])));
+                          ntohl(get_16aligned_be32(&md1_ctx->context[i])));
         }
         break;
     }
-    case NSH_M_TYPE2:
+    case NSH_M_TYPE2: {
+        const struct nsh_md2_tlv *md2_ctx = NSH_MD2_CTX(nsh_hdr);
         ds_put_cstr(ds, ",md2=");
-        ds_put_hex(ds, encap_nsh->metadata, encap_nsh->mdlen);
+        ds_put_hex(ds, md2_ctx, mdlen);
         break;
+    }
     default:
         OVS_NOT_REACHED();
     }
@@ -1057,11 +1066,16 @@  format_odp_action(struct ds *ds, const struct nlattr *a,
     case OVS_ACTION_ATTR_CLONE:
         format_odp_clone_action(ds, a, portno_names);
         break;
-    case OVS_ACTION_ATTR_ENCAP_NSH:
-        format_odp_encap_nsh_action(ds, nl_attr_get(a));
+    case OVS_ACTION_ATTR_PUSH_NSH: {
+        uint8_t buffer[256];
+        struct nsh_hdr *nsh_hdr = ALIGNED_CAST(struct nsh_hdr *, buffer);
+        const struct nsh_hdr *nsh_hdr_src = nsh_hdr;
+        odp_nsh_hdr_from_attr(nl_attr_get(a), nsh_hdr);
+        format_odp_push_nsh_action(ds, nsh_hdr_src);
         break;
-    case OVS_ACTION_ATTR_DECAP_NSH:
-        ds_put_cstr(ds, "decap_nsh()");
+    }
+    case OVS_ACTION_ATTR_POP_NSH:
+        ds_put_cstr(ds, "pop_nsh()");
         break;
     case OVS_ACTION_ATTR_UNSPEC:
     case __OVS_ACTION_ATTR_MAX:
@@ -1780,27 +1794,74 @@  find_end:
     return s - s_;
 }
 
+static void
+nsh_key_to_attr(struct ofpbuf *buf, const struct flow_nsh *nsh,
+                uint8_t * metadata, size_t md_size,
+                bool is_mask)
+{
+    size_t nsh_key_ofs;
+    struct ovs_nsh_key_base base;
+    struct ovs_nsh_key_md1 md1;
+
+    base.flags = nsh->flags;
+    base.mdtype = nsh->mdtype;
+    base.np = nsh->np;
+    base.path_hdr = htonl((ntohl(nsh->spi) << NSH_SPI_SHIFT) |
+                          nsh->si);
+
+    nsh_key_ofs = nl_msg_start_nested(buf, OVS_KEY_ATTR_NSH);
+    nl_msg_put_unspec(buf, OVS_NSH_KEY_ATTR_BASE, &base, sizeof base);
+
+    if (is_mask) {
+        for (int i = 0; i < 4; i++) {
+            md1.context[i] = nsh->context[i];
+        }
+        nl_msg_put_unspec(buf, OVS_NSH_KEY_ATTR_MD1, &md1, sizeof md1);
+    } else {
+        switch (nsh->mdtype) {
+        case NSH_M_TYPE1:
+            for (int i = 0; i < 4; i++) {
+                md1.context[i] = nsh->context[i];
+            }
+            nl_msg_put_unspec(buf, OVS_NSH_KEY_ATTR_MD1, &md1, sizeof md1);
+            break;
+        case NSH_M_TYPE2:
+            if (metadata && md_size > 0) {
+                nl_msg_put_unspec(buf, OVS_NSH_KEY_ATTR_MD2, metadata,
+                                  md_size);
+            }
+            break;
+        default:
+            /* No match support for other MD formats yet. */
+            break;
+        }
+    }
+    nl_msg_end_nested(buf, nsh_key_ofs);
+}
+
+
 static int
-parse_odp_encap_nsh_action(const char *s, struct ofpbuf *actions)
+parse_odp_push_nsh_action(const char *s, struct ofpbuf *actions)
 {
     int n = 0;
     int ret = 0;
-    struct ovs_action_encap_nsh encap_nsh;
-    uint32_t spi;
-    uint8_t si;
     uint32_t cd;
+    struct flow_nsh nsh;
+    uint8_t *metadata = NULL;
+    uint8_t md_size = 0;
 
-    if (!ovs_scan_len(s, &n, "encap_nsh(")) {
+    if (!ovs_scan_len(s, &n, "push_nsh(")) {
         ret = -EINVAL;
         goto out;
     }
 
     /* The default is NSH_M_TYPE1 */
-    encap_nsh.flags = 0;
-    encap_nsh.mdtype = NSH_M_TYPE1;
-    encap_nsh.mdlen = NSH_M_TYPE1_MDLEN;
-    encap_nsh.path_hdr = htonl(255);
-    memset(encap_nsh.metadata, 0, NSH_M_TYPE1_MDLEN);
+    nsh.flags = 0;
+    nsh.mdtype = NSH_M_TYPE1;
+    nsh.np = NSH_P_ETHERNET;
+    nsh.spi = 0;
+    nsh.si = 255;
+    memset(nsh.context, 0, NSH_M_TYPE1_MDLEN);
 
     for (;;) {
         n += strspn(s + n, delimiters);
@@ -1808,17 +1869,17 @@  parse_odp_encap_nsh_action(const char *s, struct ofpbuf *actions)
             break;
         }
 
-        if (ovs_scan_len(s, &n, "flags=%"SCNi8, &encap_nsh.flags)) {
+        if (ovs_scan_len(s, &n, "flags=%"SCNi8, &nsh.flags)) {
             continue;
         }
-        if (ovs_scan_len(s, &n, "mdtype=%"SCNi8, &encap_nsh.mdtype)) {
-            switch (encap_nsh.mdtype) {
+        if (ovs_scan_len(s, &n, "mdtype=%"SCNi8, &nsh.mdtype)) {
+            switch (nsh.mdtype) {
             case NSH_M_TYPE1:
                 /* This is the default format. */;
                 break;
             case NSH_M_TYPE2:
                 /* Length will be updated later. */
-                encap_nsh.mdlen = 0;
+                md_size = 0;
                 break;
             default:
                 ret = -EINVAL;
@@ -1826,65 +1887,60 @@  parse_odp_encap_nsh_action(const char *s, struct ofpbuf *actions)
             }
             continue;
         }
-        if (ovs_scan_len(s, &n, "np=%"SCNi8, &encap_nsh.np)) {
+        if (ovs_scan_len(s, &n, "np=%"SCNi8, &nsh.np)) {
             continue;
         }
-        if (ovs_scan_len(s, &n, "spi=0x%"SCNx32, &spi)) {
-            encap_nsh.path_hdr =
-                    htonl(((spi << NSH_SPI_SHIFT) & NSH_SPI_MASK) |
-                            (ntohl(encap_nsh.path_hdr) & ~NSH_SPI_MASK));
+        if (ovs_scan_len(s, &n, "spi=0x%"SCNx32, &nsh.spi)) {
+            nsh.spi = htonl(nsh.spi);
             continue;
         }
-        if (ovs_scan_len(s, &n, "si=%"SCNi8, &si)) {
-            encap_nsh.path_hdr =
-                    htonl((si << NSH_SI_SHIFT) |
-                            (ntohl(encap_nsh.path_hdr) & ~NSH_SI_MASK));
+        if (ovs_scan_len(s, &n, "si=%"SCNi8, &nsh.si)) {
             continue;
         }
-        if (encap_nsh.mdtype == NSH_M_TYPE1) {
-            struct nsh_md1_ctx *md1 =
-                ALIGNED_CAST(struct nsh_md1_ctx *, encap_nsh.metadata);
+        if (nsh.mdtype == NSH_M_TYPE1) {
             if (ovs_scan_len(s, &n, "c1=0x%"SCNx32, &cd)) {
-                put_16aligned_be32(&md1->c[0], htonl(cd));
+                nsh.context[0] = htonl(cd);
                 continue;
             }
             if (ovs_scan_len(s, &n, "c2=0x%"SCNx32, &cd)) {
-                put_16aligned_be32(&md1->c[1], htonl(cd));
+                nsh.context[1] = htonl(cd);
                 continue;
             }
             if (ovs_scan_len(s, &n, "c3=0x%"SCNx32, &cd)) {
-                put_16aligned_be32(&md1->c[2], htonl(cd));
+                nsh.context[2] = htonl(cd);
                 continue;
             }
             if (ovs_scan_len(s, &n, "c4=0x%"SCNx32, &cd)) {
-                put_16aligned_be32(&md1->c[3], htonl(cd));
+                nsh.context[3] = htonl(cd);
                 continue;
             }
         }
-        else if (encap_nsh.mdtype == NSH_M_TYPE2) {
+        else if (nsh.mdtype == NSH_M_TYPE2) {
             struct ofpbuf b;
             char buf[512];
             size_t mdlen;
             if (ovs_scan_len(s, &n, "md2=0x%511[0-9a-fA-F]", buf)) {
-                ofpbuf_use_stub(&b, encap_nsh.metadata,
-                                OVS_ENCAP_NSH_MAX_MD_LEN);
+                metadata = xmalloc(NSH_M_TYPE2_MAX_LEN - 8);
+                ofpbuf_use_stub(&b, metadata,
+                                NSH_M_TYPE2_MAX_LEN - 8);
                 ofpbuf_put_hex(&b, buf, &mdlen);
-                encap_nsh.mdlen = mdlen;
+                md_size = mdlen;
                 ofpbuf_uninit(&b);
             }
             continue;
         }
     }
 out:
-    if (ret < 0) {
-        return ret;
-    } else {
-        size_t size = offsetof(struct ovs_action_encap_nsh, metadata)
-                + ROUND_UP(encap_nsh.mdlen, 4);
-        nl_msg_put_unspec(actions, OVS_ACTION_ATTR_ENCAP_NSH,
-                          &encap_nsh, size);
-        return n;
+    if (ret >= 0) {
+        size_t offset = nl_msg_start_nested(actions, OVS_ACTION_ATTR_PUSH_NSH);
+        nsh_key_to_attr(actions, &nsh, metadata, md_size, false);
+        nl_msg_end_nested(actions, offset);
+        ret = n;
     }
+    if (metadata != NULL) {
+        free(metadata);
+    }
+    return ret;
 }
 
 static int
@@ -2089,8 +2145,8 @@  parse_odp_action(const char *s, const struct simap *port_names,
     }
 
     {
-        if (!strncmp(s, "encap_nsh(", 10)) {
-            int retval = parse_odp_encap_nsh_action(s, actions);
+        if (!strncmp(s, "push_nsh(", 9)) {
+            int retval = parse_odp_push_nsh_action(s, actions);
             if (retval < 0) {
                 return retval;
             }
@@ -2100,8 +2156,8 @@  parse_odp_action(const char *s, const struct simap *port_names,
 
     {
         int n;
-        if (ovs_scan(s, "decap_nsh()%n", &n)) {
-            nl_msg_put_flag(actions, OVS_ACTION_ATTR_DECAP_NSH);
+        if (ovs_scan(s, "pop_nsh()%n", &n)) {
+            nl_msg_put_flag(actions, OVS_ACTION_ATTR_POP_NSH);
             return n;
         }
     }
@@ -2198,6 +2254,13 @@  static const struct attr_len_tbl ovs_tun_key_attr_lens[OVS_TUNNEL_KEY_ATTR_MAX +
     [OVS_TUNNEL_KEY_ATTR_IPV6_DST]      = { .len = 16 },
 };
 
+static const struct attr_len_tbl
+ovs_nsh_key_attr_lens[OVS_NSH_KEY_ATTR_MAX + 1] = {
+    [OVS_NSH_KEY_ATTR_BASE]     = { .len = 8 },
+    [OVS_NSH_KEY_ATTR_MD1]      = { .len = 16 },
+    [OVS_NSH_KEY_ATTR_MD2]      = { .len = ATTR_LEN_VARIABLE },
+};
+
 static const struct attr_len_tbl ovs_flow_key_attr_lens[OVS_KEY_ATTR_MAX + 1] = {
     [OVS_KEY_ATTR_ENCAP]     = { .len = ATTR_LEN_NESTED },
     [OVS_KEY_ATTR_PRIORITY]  = { .len = 4 },
@@ -2229,7 +2292,9 @@  static const struct attr_len_tbl ovs_flow_key_attr_lens[OVS_KEY_ATTR_MAX + 1] =
     [OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4] = { .len = sizeof(struct ovs_key_ct_tuple_ipv4) },
     [OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6] = { .len = sizeof(struct ovs_key_ct_tuple_ipv6) },
     [OVS_KEY_ATTR_PACKET_TYPE] = { .len = 4  },
-    [OVS_KEY_ATTR_NSH]       = { .len = sizeof(struct ovs_key_nsh) },
+    [OVS_KEY_ATTR_NSH]       = { .len = ATTR_LEN_NESTED,
+                                 .next = ovs_nsh_key_attr_lens,
+                                 .next_max = OVS_NSH_KEY_ATTR_MAX },
 };
 
 /* Returns the correct length of the payload for a flow key attribute of the
@@ -2280,6 +2345,135 @@  ovs_frag_type_to_string(enum ovs_frag_type type)
     }
 }
 
+enum odp_key_fitness
+odp_nsh_hdr_from_attr(const struct nlattr *attr,
+                      struct nsh_hdr *nsh_hdr)
+{
+    unsigned int left;
+    const struct nlattr *a;
+    bool unknown = false;
+    uint8_t flags = 0;
+    size_t mdlen = 0;
+    bool has_md1 = false;
+    bool has_md2 = false;
+
+    NL_NESTED_FOR_EACH (a, left, attr) {
+        uint16_t type = nl_attr_type(a);
+        size_t len = nl_attr_get_size(a);
+        int expected_len = odp_key_attr_len(ovs_nsh_key_attr_lens,
+                                            OVS_NSH_KEY_ATTR_MAX, type);
+
+        if (len != expected_len && expected_len >= 0) {
+            return ODP_FIT_ERROR;
+        }
+
+        switch (type) {
+        case OVS_NSH_KEY_ATTR_BASE: {
+            const struct ovs_nsh_key_base *base = nl_attr_get(a);
+            nsh_hdr->next_proto = base->np;
+            nsh_hdr->md_type = base->mdtype;
+            put_16aligned_be32(&nsh_hdr->path_hdr, base->path_hdr);
+            flags = base->flags;
+            break;
+        }
+        case OVS_NSH_KEY_ATTR_MD1: {
+            const struct ovs_nsh_key_md1 *md1 = nl_attr_get(a);
+            struct nsh_md1_ctx *md1_dst = nsh_md1_ctx(nsh_hdr);
+            has_md1 = true;
+            mdlen = sizeof *md1;
+            memcpy(md1_dst, md1, mdlen);
+            break;
+        }
+        case OVS_NSH_KEY_ATTR_MD2: {
+            struct nsh_md2_tlv *md2_dst = nsh_md2_ctx(nsh_hdr);
+            const uint8_t *md2 = nl_attr_get(a);
+            has_md2 = true;
+            mdlen = nl_attr_get_size(a);
+            memcpy(md2_dst, md2, mdlen);
+            break;
+        }
+        default:
+            /* Allow this to show up as unexpected, if there are unknown
+             * tunnel attribute, eventually resulting in ODP_FIT_TOO_MUCH. */
+            unknown = true;
+            break;
+        }
+    }
+
+    if (unknown) {
+        return ODP_FIT_TOO_MUCH;
+    }
+
+    if ((has_md1 && nsh_hdr->md_type != NSH_M_TYPE1)
+        || (has_md2 && nsh_hdr->md_type != NSH_M_TYPE2)) {
+        return ODP_FIT_ERROR;
+    }
+
+    /* nsh header length  = NSH_BASE_HDR_LEN + mdlen */
+    nsh_hdr->ver_flags_len = htons(flags << NSH_FLAGS_SHIFT |
+                               (NSH_BASE_HDR_LEN + mdlen) >> 2);
+
+    return ODP_FIT_PERFECT;
+}
+
+enum odp_key_fitness
+odp_nsh_key_from_attr(const struct nlattr *attr, struct flow_nsh *nsh)
+{
+    unsigned int left;
+    const struct nlattr *a;
+    bool unknown = false;
+    bool has_md1 = false;
+
+    NL_NESTED_FOR_EACH (a, left, attr) {
+        uint16_t type = nl_attr_type(a);
+        size_t len = nl_attr_get_size(a);
+        int expected_len = odp_key_attr_len(ovs_nsh_key_attr_lens,
+                                            OVS_NSH_KEY_ATTR_MAX, type);
+
+        if (len != expected_len && expected_len >= 0) {
+            return ODP_FIT_ERROR;
+        }
+
+        switch (type) {
+        case OVS_NSH_KEY_ATTR_BASE: {
+            const struct ovs_nsh_key_base *base = nl_attr_get(a);
+            nsh->flags = base->flags;
+            nsh->mdtype = base->mdtype;
+            nsh->np = base->np;
+            nsh->spi = htonl((ntohl(base->path_hdr) & NSH_SPI_MASK) >>
+                                 NSH_SPI_SHIFT);
+            nsh->si = (ntohl(base->path_hdr) & NSH_SI_MASK) >> NSH_SI_SHIFT;
+            break;
+        }
+        case OVS_NSH_KEY_ATTR_MD1: {
+            const struct ovs_nsh_key_md1 *md1 = nl_attr_get(a);
+            has_md1 = true;
+            nsh->context[0] = md1->context[0];
+            nsh->context[1] = md1->context[1];
+            nsh->context[2] = md1->context[2];
+            nsh->context[3] = md1->context[3];
+            break;
+        }
+        case OVS_NSH_KEY_ATTR_MD2:
+        default:
+            /* Allow this to show up as unexpected, if there are unknown
+             * tunnel attribute, eventually resulting in ODP_FIT_TOO_MUCH. */
+            unknown = true;
+            break;
+        }
+    }
+
+    if (unknown) {
+        return ODP_FIT_TOO_MUCH;
+    }
+
+    if (has_md1 && nsh->mdtype != NSH_M_TYPE1) {
+        return ODP_FIT_ERROR;
+    }
+
+    return ODP_FIT_PERFECT;
+}
+
 static enum odp_key_fitness
 odp_tun_key_from_attr__(const struct nlattr *attr, bool is_mask,
                         struct flow_tnl *tun)
@@ -2971,6 +3165,80 @@  format_odp_tun_geneve(const struct nlattr *attr,
 }
 
 static void
+format_odp_nsh_attr(const struct nlattr *attr, const struct nlattr *mask_attr,
+                    struct ds *ds)
+{
+    unsigned int left;
+    const struct nlattr *a;
+    struct ovs_key_nsh nsh;
+    struct ovs_key_nsh nsh_mask;
+
+    memset(&nsh, 0, sizeof nsh);
+    memset(&nsh_mask, 0xff, sizeof nsh_mask);
+
+    NL_NESTED_FOR_EACH (a, left, attr) {
+        enum ovs_nsh_key_attr type = nl_attr_type(a);
+        const struct nlattr *ma = NULL;
+
+        if (mask_attr) {
+            ma = nl_attr_find__(nl_attr_get(mask_attr),
+                                nl_attr_get_size(mask_attr), type);
+        }
+
+        if (!check_attr_len(ds, a, ma, ovs_nsh_key_attr_lens,
+                            OVS_NSH_KEY_ATTR_MAX, true)) {
+            continue;
+        }
+
+        switch (type) {
+        case OVS_NSH_KEY_ATTR_BASE: {
+            const struct ovs_nsh_key_base * base = nl_attr_get(a);
+            const struct ovs_nsh_key_base * base_mask
+                = ma ? nl_attr_get(ma) : NULL;
+            nsh.flags = base->flags;
+            nsh.mdtype = base->mdtype;
+            nsh.np = base->np;
+            nsh.path_hdr = base->path_hdr;
+            if (base_mask) {
+                nsh_mask.flags = base_mask->flags;
+                nsh_mask.mdtype = base_mask->mdtype;
+                nsh_mask.np = base_mask->np;
+                nsh_mask.path_hdr = base_mask->path_hdr;
+            }
+            break;
+        }
+        case OVS_NSH_KEY_ATTR_MD1: {
+            const struct ovs_nsh_key_md1 * md1 = nl_attr_get(a);
+            const struct ovs_nsh_key_md1 * md1_mask
+                = ma ? nl_attr_get(ma) : NULL;
+            nsh.context[0] = md1->context[0];
+            nsh.context[1] = md1->context[1];
+            nsh.context[2] = md1->context[2];
+            nsh.context[3] = md1->context[3];
+            if (md1_mask) {
+                nsh_mask.context[0] = md1_mask->context[0];
+                nsh_mask.context[1] = md1_mask->context[1];
+                nsh_mask.context[2] = md1_mask->context[2];
+                nsh_mask.context[3] = md1_mask->context[3];
+            }
+            break;
+        }
+        case OVS_NSH_KEY_ATTR_MD2:
+        case __OVS_NSH_KEY_ATTR_MAX:
+        default:
+            /* No support for matching other metadata formats yet. */
+            break;
+        }
+    }
+
+    if (mask_attr) {
+        format_nsh_key_mask(ds, &nsh, &nsh_mask);
+    } else {
+        format_nsh_key(ds, &nsh);
+    }
+}
+
+static void
 format_odp_tun_attr(const struct nlattr *attr, const struct nlattr *mask_attr,
                     struct ds *ds, bool verbose)
 {
@@ -3448,9 +3716,7 @@  format_odp_key_attr__(const struct nlattr *a, const struct nlattr *ma,
         break;
     }
     case OVS_KEY_ATTR_NSH: {
-        const struct ovs_key_nsh *mask = ma ? nl_attr_get(ma) : NULL;
-        const struct ovs_key_nsh *key = nl_attr_get(a);
-        format_nsh_key_mask(ds, key, mask);
+        format_odp_nsh_attr(a, ma, ds);
         break;
     }
     case OVS_KEY_ATTR_UNSPEC:
@@ -4549,6 +4815,129 @@  geneve_to_attr(struct ofpbuf *a, const void *data_)
     } SCAN_END_SINGLE(ATTR)
 
 static int
+parse_odp_nsh_key_mask_attr(const char *s, struct ofpbuf *key,
+                            struct ofpbuf *mask)
+{
+    if (strncmp(s, "nsh(", 4) == 0) {
+        const char *start = s;
+        int len;
+        struct flow_nsh skey, smask;
+
+        s += 4;
+
+        memset(&skey, 0, sizeof skey);
+        memset(&smask, 0, sizeof smask);
+        do {
+            len = 0;
+
+            if (strncmp(s, "flags=", 6) == 0) {
+                s += 6;
+                len = scan_u8(s, &skey.flags, mask ? &smask.flags : NULL);
+                if (len == 0) {
+                    return -EINVAL;
+                }
+                s += len;
+                continue;
+            }
+
+            if (strncmp(s, "mdtype=", 7) == 0) {
+                s += 7;
+                len = scan_u8(s, &skey.mdtype, mask ? &smask.mdtype : NULL);
+                if (len == 0) {
+                    return -EINVAL;
+                }
+                s += len;
+                continue;
+            }
+
+            if (strncmp(s, "np=", 3) == 0) {
+                s += 3;
+                len = scan_u8(s, &skey.np, mask ? &smask.np : NULL);
+                if (len == 0) {
+                    return -EINVAL;
+                }
+                s += len;
+                continue;
+            }
+
+            if (strncmp(s, "spi=", 4) == 0) {
+                s += 4;
+                len = scan_be32(s, &skey.spi, mask ? &smask.spi : NULL);
+                if (len == 0) {
+                    return -EINVAL;
+                }
+                s += len;
+                continue;
+            }
+
+            if (strncmp(s, "si=", 3) == 0) {
+                s += 3;
+                len = scan_u8(s, &skey.si, mask ? &smask.si : NULL);
+                if (len == 0) {
+                    return -EINVAL;
+                }
+                s += len;
+                continue;
+            }
+
+            if (strncmp(s, "c1=", 3) == 0) {
+                s += 3;
+                len = scan_be32(s, &skey.context[0],
+                                mask ? &smask.context[0] : NULL);
+                if (len == 0) {
+                    return -EINVAL;
+                }
+                s += len;
+                continue;
+            }
+
+            if (strncmp(s, "c2=", 3) == 0) {
+                s += 3;
+                len = scan_be32(s, &skey.context[1],
+                                mask ? &smask.context[1] : NULL);
+                if (len == 0) {
+                    return -EINVAL;
+                }
+                s += len;
+                continue;
+            }
+
+            if (strncmp(s, "c3=", 3) == 0) {
+                s += 3;
+                len = scan_be32(s, &skey.context[2],
+                                mask ? &smask.context[2] : NULL);
+                if (len == 0) {
+                    return -EINVAL;
+                }
+                s += len;
+                continue;
+            }
+
+            if (strncmp(s, "c4=", 3) == 0) {
+                s += 3;
+                len = scan_be32(s, &skey.context[3],
+                                mask ? &smask.context[3] : NULL);
+                if (len == 0) {
+                    return -EINVAL;
+                }
+                s += len;
+                continue;
+            }
+        } while (*s++ == ',' && len != 0);
+        if (s[-1] != ')') {
+            return -EINVAL;
+        }
+
+        nsh_key_to_attr(key, &skey, NULL, 0, false);
+        if (mask) {
+            nsh_key_to_attr(mask, &smask, NULL, 0, true);
+        }
+        return s - start;
+    }
+    return 0;
+}
+
+static int
 parse_odp_key_mask_attr(const char *s, const struct simap *port_names,
                         struct ofpbuf *key, struct ofpbuf *mask)
 {
@@ -4694,16 +5083,13 @@  parse_odp_key_mask_attr(const char *s, const struct simap *port_names,
         SCAN_FIELD("id=", be16, id);
     } SCAN_END(OVS_KEY_ATTR_PACKET_TYPE);
 
-    SCAN_BEGIN("nsh(", struct ovs_key_nsh) {
-        SCAN_FIELD("flags=", u8, flags);
-        SCAN_FIELD("mdtype=", u8, mdtype);
-        SCAN_FIELD("np=", u8, np);
-        SCAN_FIELD("path_hdr=", be32, path_hdr);
-        SCAN_FIELD("c1=", be32, c[0]);
-        SCAN_FIELD("c2=", be32, c[1]);
-        SCAN_FIELD("c3=", be32, c[2]);
-        SCAN_FIELD("c4=", be32, c[2]);
-    } SCAN_END(OVS_KEY_ATTR_NSH);
+    /* nsh is nested, it needs special process */
+    int ret = parse_odp_nsh_key_mask_attr(s, key, mask);
+    if (ret < 0) {
+       return ret;
+    } else {
+       s += ret;
+    }
 
     /* Encap open-coded. */
     if (!strncmp(s, "encap(", 6)) {
@@ -4994,11 +5380,7 @@  odp_flow_key_from_flow__(const struct odp_flow_key_parms *parms,
             mpls_key[i].mpls_lse = data->mpls_lse[i];
         }
     } else if (flow->dl_type == htons(ETH_TYPE_NSH)) {
-        struct ovs_key_nsh *nsh_key;
-
-        nsh_key = nl_msg_put_unspec_uninit(buf, OVS_KEY_ATTR_NSH,
-                                            sizeof *nsh_key);
-        get_nsh_key(data, nsh_key, export_mask);
+        nsh_key_to_attr(buf, &data->nsh, NULL, 0, export_mask);
     }
 
     if (is_ip_any(flow) && !(flow->nw_frag & FLOW_NW_FRAG_LATER)) {
@@ -5558,13 +5940,10 @@  parse_l2_5_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
             expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_NSH;
         }
         if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_NSH)) {
-            const struct ovs_key_nsh *nsh_key;
-
-            nsh_key = nl_attr_get(attrs[OVS_KEY_ATTR_NSH]);
-            put_nsh_key(nsh_key, flow, false);
+            odp_nsh_key_from_attr(attrs[OVS_KEY_ATTR_NSH], &flow->nsh);
             if (is_mask) {
-                check_start = nsh_key;
-                check_len = sizeof *nsh_key;
+                check_start = nl_attr_get(attrs[OVS_KEY_ATTR_NSH]);
+                check_len = nl_attr_get_size(attrs[OVS_KEY_ATTR_NSH]);
                 expected_bit = OVS_KEY_ATTR_NSH;
             }
         }
@@ -6620,13 +6999,13 @@  get_nsh_key(const struct flow *flow, struct ovs_key_nsh *nsh, bool is_mask)
                           flow->nsh.si);
     if (is_mask) {
         for (int i = 0; i < 4; i++) {
-            nsh->c[i] = flow->nsh.c[i];
+            nsh->context[i] = flow->nsh.context[i];
         }
     } else {
         switch (nsh->mdtype) {
         case NSH_M_TYPE1:
             for (int i = 0; i < 4; i++) {
-                nsh->c[i] = flow->nsh.c[i];
+                nsh->context[i] = flow->nsh.context[i];
             }
             break;
         case NSH_M_TYPE2:
@@ -6650,17 +7029,125 @@  put_nsh_key(const struct ovs_key_nsh *nsh, struct flow *flow,
     switch (nsh->mdtype) {
         case NSH_M_TYPE1:
             for (int i = 0; i < 4; i++) {
-                flow->nsh.c[i] = nsh->c[i];
+                flow->nsh.context[i] = nsh->context[i];
             }
             break;
         case NSH_M_TYPE2:
         default:
             /* No match support for other MD formats yet. */
-            memset(flow->nsh.c, 0, sizeof flow->nsh.c);
+            memset(flow->nsh.context, 0, sizeof flow->nsh.context);
             break;
     }
 }
 
+static bool
+commit_nsh(const struct flow_nsh * flow_nsh, bool use_masked_set,
+           const struct ovs_key_nsh *key, struct ovs_key_nsh *base,
+           struct ovs_key_nsh *mask, size_t size,
+           struct ofpbuf *odp_actions)
+{
+    enum ovs_key_attr attr = OVS_KEY_ATTR_NSH;
+
+    if (memcmp(key, base, size)  == 0) {
+        /* Mask bits are set when we have either read or set the corresponding
+         * values.  Masked bits will be exact-matched, no need to set them
+         * if the value did not actually change. */
+        return false;
+    }
+
+    bool fully_masked = odp_mask_is_exact(attr, mask, size);
+
+    if (use_masked_set && !fully_masked) {
+        size_t nsh_key_ofs;
+        struct ovs_nsh_key_base nsh_base;
+        struct ovs_nsh_key_base nsh_base_mask;
+        struct ovs_nsh_key_md1 md1;
+        struct ovs_nsh_key_md1 md1_mask;
+        size_t offset = nl_msg_start_nested(odp_actions,
+                                            OVS_ACTION_ATTR_SET_MASKED);
+
+        nsh_base.flags = key->flags;
+        nsh_base.mdtype = key->mdtype;
+        nsh_base.np = key->np;
+        nsh_base.path_hdr = key->path_hdr;
+
+        nsh_base_mask.flags = mask->flags;
+        nsh_base_mask.mdtype = mask->mdtype;
+        nsh_base_mask.np = mask->np;
+        nsh_base_mask.path_hdr = mask->path_hdr;
+
+        /* OVS_KEY_ATTR_NSH keys */
+        nsh_key_ofs = nl_msg_start_nested(odp_actions, OVS_KEY_ATTR_NSH);
+
+        char *data = nl_msg_put_unspec_uninit(odp_actions,
+                                              OVS_NSH_KEY_ATTR_BASE,
+                                              sizeof(nsh_base));
+        const char *lkey = (char *)&nsh_base, *lmask = (char *)&nsh_base_mask;
+        size_t lkey_size = sizeof(nsh_base);
+
+        while (lkey_size--) {
+            *data++ = *lkey++ & *lmask++;
+        }
+
+        switch (key->mdtype) {
+        case NSH_M_TYPE1:
+            for (int i = 0; i < 4; i++) {
+                md1.context[i] = key->context[i];
+                md1_mask.context[i] = mask->context[i];
+            }
+            data = nl_msg_put_unspec_uninit(odp_actions,
+                                            OVS_NSH_KEY_ATTR_MD1,
+                                            sizeof(md1));
+            lkey = (char *)&md1;
+            lmask = (char *)&md1_mask;
+            lkey_size = sizeof(md1);
+
+            while (lkey_size--) {
+                *data++ = *lkey++ & *lmask++;
+            }
+            break;
+        case NSH_M_TYPE2:
+        default:
+            /* No match support for other MD formats yet. */
+            break;
+        }
+
+        /* OVS_KEY_ATTR_NSH masks */
+        data = nl_msg_put_unspec_uninit(odp_actions,
+                                        OVS_NSH_KEY_ATTR_BASE,
+                                        sizeof(nsh_base_mask));
+        lmask = (char *)&nsh_base_mask;
+
+        memcpy(data, lmask, sizeof(nsh_base_mask));
+
+        switch (key->mdtype) {
+        case NSH_M_TYPE1:
+            data = nl_msg_put_unspec_uninit(odp_actions,
+                                            OVS_NSH_KEY_ATTR_MD1,
+                                            sizeof(md1_mask));
+            lmask = (char *)&md1_mask;
+            memcpy(data, lmask, sizeof(md1_mask));
+            break;
+        case NSH_M_TYPE2:
+        default:
+            /* No match support for other MD formats yet. */
+            break;
+        }
+        nl_msg_end_nested(odp_actions, nsh_key_ofs);
+
+        nl_msg_end_nested(odp_actions, offset);
+    } else {
+        if (!fully_masked) {
+            memset(mask, 0xff, size);
+        }
+        size_t offset = nl_msg_start_nested(odp_actions, OVS_ACTION_ATTR_SET);
+        nsh_key_to_attr(odp_actions, flow_nsh, NULL, 0, false);
+        nl_msg_end_nested(odp_actions, offset);
+    }
+    memcpy(base, key, size);
+    return true;
+}
+
 static void
 commit_set_nsh_action(const struct flow *flow, struct flow *base_flow,
                       struct ofpbuf *odp_actions,
@@ -6684,8 +7171,8 @@  commit_set_nsh_action(const struct flow *flow, struct flow *base_flow,
     mask.mdtype = 0;     /* Not writable. */
     mask.np = 0;         /* Not writable. */
 
-    if (commit(OVS_KEY_ATTR_NSH, use_masked, &key, &base, &mask, sizeof key,
-               odp_actions)) {
+    if (commit_nsh(&base_flow->nsh, use_masked, &key, &base, &mask,
+            sizeof key, odp_actions)) {
         put_nsh_key(&base, base_flow, false);
         if (mask.mdtype != 0) { /* Mask was changed by commit(). */
             put_nsh_key(&mask, &wc->masks, true);
@@ -6788,49 +7275,36 @@  commit_set_pkt_mark_action(const struct flow *flow, struct flow *base_flow,
 }
 
 static void
-odp_put_decap_nsh_action(struct ofpbuf *odp_actions)
+odp_put_pop_nsh_action(struct ofpbuf *odp_actions)
 {
-    nl_msg_put_flag(odp_actions, OVS_ACTION_ATTR_DECAP_NSH);
+    nl_msg_put_flag(odp_actions, OVS_ACTION_ATTR_POP_NSH);
 }
 
 static void
-odp_put_encap_nsh_action(struct ofpbuf *odp_actions,
+odp_put_push_nsh_action(struct ofpbuf *odp_actions,
                          const struct flow *flow,
                          struct ofpbuf *encap_data)
 {
-    struct ovs_action_encap_nsh encap_nsh;
-
-    encap_nsh.flags = flow->nsh.flags;
-    encap_nsh.mdtype = flow->nsh.mdtype;
-    encap_nsh.np = flow->nsh.np;
-    encap_nsh.path_hdr = htonl((ntohl(flow->nsh.spi) << NSH_SPI_SHIFT) |
-                                   flow->nsh.si);
+    uint8_t * metadata = NULL;
+    uint8_t md_size = 0;
 
-    switch (encap_nsh.mdtype) {
-    case NSH_M_TYPE1: {
-        struct nsh_md1_ctx *md1 =
-            ALIGNED_CAST(struct nsh_md1_ctx *, encap_nsh.metadata);
-        encap_nsh.mdlen = NSH_M_TYPE1_MDLEN;
-        for (int i = 0; i < 4; i++) {
-            put_16aligned_be32(&md1->c[i], flow->nsh.c[i]);
-        }
-        break;
-    }
+    switch (flow->nsh.mdtype) {
     case NSH_M_TYPE2:
         if (encap_data) {
-            ovs_assert(encap_data->size < OVS_ENCAP_NSH_MAX_MD_LEN);
-            encap_nsh.mdlen = encap_data->size;
-            memcpy(encap_nsh.metadata, encap_data->data, encap_data->size);
+            ovs_assert(encap_data->size < OVS_PUSH_NSH_MAX_MD_LEN);
+            metadata = encap_data->data;
+            md_size = encap_data->size;
         } else {
-            encap_nsh.mdlen = 0;
+            md_size = 0;
         }
         break;
     default:
-        encap_nsh.mdlen = 0;
+        md_size = 0;
         break;
     }
-    nl_msg_put_unspec(odp_actions, OVS_ACTION_ATTR_ENCAP_NSH,
-                      &encap_nsh, sizeof(encap_nsh));
+    size_t offset = nl_msg_start_nested(odp_actions, OVS_ACTION_ATTR_PUSH_NSH);
+    nsh_key_to_attr(odp_actions, &flow->nsh, metadata, md_size, false);
+    nl_msg_end_nested(odp_actions, offset);
 }
 
 static void
@@ -6857,8 +7331,8 @@  commit_packet_type_change(const struct flow *flow,
             break;
         }
         case PT_NSH:
-            /* encap_nsh */
-            odp_put_encap_nsh_action(odp_actions, flow, encap_data);
+            /* push_nsh */
+            odp_put_push_nsh_action(odp_actions, flow, encap_data);
             base_flow->packet_type = flow->packet_type;
             /* Update all packet headers in base_flow. */
             memcpy(&base_flow->dl_dst, &flow->dl_dst,
@@ -6883,8 +7357,8 @@  commit_packet_type_change(const struct flow *flow,
              * No need to update the base flow here. */
             switch (ntohl(base_flow->packet_type)) {
             case PT_NSH:
-                /* decap_nsh. */
-                odp_put_decap_nsh_action(odp_actions);
+                /* pop_nsh. */
+                odp_put_pop_nsh_action(odp_actions);
                 break;
             default:
                 /* Checks are done during translation. */
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 27c2ab4..bd6be60 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -158,6 +158,10 @@  struct odputil_keybuf {
 
 enum odp_key_fitness odp_tun_key_from_attr(const struct nlattr *,
                                            struct flow_tnl *);
+enum odp_key_fitness odp_nsh_key_from_attr(const struct nlattr *,
+                                           struct flow_nsh *);
+enum odp_key_fitness odp_nsh_hdr_from_attr(const struct nlattr *,
+                                           struct nsh_hdr *);
 
 int odp_ufid_from_string(const char *s_, ovs_u128 *ufid);
 void odp_format_ufid(const ovs_u128 *ufid, struct ds *);
diff --git a/lib/packets.c b/lib/packets.c
index 74d87ed..db98ab3 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -403,10 +403,10 @@  pop_mpls(struct dp_packet *packet, ovs_be16 ethtype)
 }
 
 void
-encap_nsh(struct dp_packet *packet, const struct ovs_action_encap_nsh *encap)
+push_nsh(struct dp_packet *packet, const struct nsh_hdr *nsh_hdr_src)
 {
     struct nsh_hdr *nsh;
-    size_t length = NSH_BASE_HDR_LEN + encap->mdlen;
+    size_t length = nsh_hdr_len(nsh_hdr_src);
     uint8_t next_proto;
 
     switch (ntohl(packet->packet_type)) {
@@ -427,23 +427,8 @@  encap_nsh(struct dp_packet *packet, const struct ovs_action_encap_nsh *encap)
     }
 
     nsh = (struct nsh_hdr *) dp_packet_push_uninit(packet, length);
-    nsh->ver_flags_len = htons(encap->flags << NSH_FLAGS_SHIFT | length >> 2);
+    memcpy(nsh, nsh_hdr_src, length);
     nsh->next_proto = next_proto;
-    put_16aligned_be32(&nsh->path_hdr, encap->path_hdr);
-    nsh->md_type = encap->mdtype;
-    switch (nsh->md_type) {
-        case NSH_M_TYPE1:
-            nsh->md1 = *ALIGNED_CAST(struct nsh_md1_ctx *, encap->metadata);
-            break;
-        case NSH_M_TYPE2: {
-            /* The MD2 metadata in encap is already padded to 4 bytes. */
-            size_t len = ROUND_UP(encap->mdlen, 4);
-            memcpy(&nsh->md2, encap->metadata, len);
-            break;
-        }
-        default:
-            OVS_NOT_REACHED();
-    }
 
     packet->packet_type = htonl(PT_NSH);
     dp_packet_reset_offsets(packet);
@@ -451,7 +436,7 @@  encap_nsh(struct dp_packet *packet, const struct ovs_action_encap_nsh *encap)
 }
 
 bool
-decap_nsh(struct dp_packet *packet)
+pop_nsh(struct dp_packet *packet)
 {
     struct nsh_hdr *nsh = (struct nsh_hdr *) dp_packet_l3(packet);
     size_t length;
diff --git a/lib/packets.h b/lib/packets.h
index 705d0b2..e7832ba 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -434,9 +434,8 @@  void push_eth(struct dp_packet *packet, const struct eth_addr *dst,
               const struct eth_addr *src);
 void pop_eth(struct dp_packet *packet);
 
-void encap_nsh(struct dp_packet *packet,
-               const struct ovs_action_encap_nsh *encap_nsh);
-bool decap_nsh(struct dp_packet *packet);
+void push_nsh(struct dp_packet *packet, const struct nsh_hdr *nsh_hdr_src);
+bool pop_nsh(struct dp_packet *packet);
 
 #define LLC_DSAP_SNAP 0xaa
 #define LLC_SSAP_SNAP 0xaa
diff --git a/ofproto/ofproto-dpif-ipfix.c b/ofproto/ofproto-dpif-ipfix.c
index 472c272..16976c4 100644
--- a/ofproto/ofproto-dpif-ipfix.c
+++ b/ofproto/ofproto-dpif-ipfix.c
@@ -2823,8 +2823,8 @@  dpif_ipfix_read_actions(const struct flow *flow,
         case OVS_ACTION_ATTR_POP_MPLS:
         case OVS_ACTION_ATTR_PUSH_ETH:
         case OVS_ACTION_ATTR_POP_ETH:
-        case OVS_ACTION_ATTR_ENCAP_NSH:
-        case OVS_ACTION_ATTR_DECAP_NSH:
+        case OVS_ACTION_ATTR_PUSH_NSH:
+        case OVS_ACTION_ATTR_POP_NSH:
         case OVS_ACTION_ATTR_UNSPEC:
         case __OVS_ACTION_ATTR_MAX:
         default:
diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
index 65a2003..1af1569 100644
--- a/ofproto/ofproto-dpif-sflow.c
+++ b/ofproto/ofproto-dpif-sflow.c
@@ -1199,8 +1199,8 @@  dpif_sflow_read_actions(const struct flow *flow,
 	    break;
 	case OVS_ACTION_ATTR_SAMPLE:
 	case OVS_ACTION_ATTR_CLONE:
-        case OVS_ACTION_ATTR_ENCAP_NSH:
-        case OVS_ACTION_ATTR_DECAP_NSH:
+        case OVS_ACTION_ATTR_PUSH_NSH:
+        case OVS_ACTION_ATTR_POP_NSH:
 	case OVS_ACTION_ATTR_UNSPEC:
 	case __OVS_ACTION_ATTR_MAX:
 	default:
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 973e760..437c519 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -4392,8 +4392,8 @@  xlate_fixup_actions(struct ofpbuf *b, const struct nlattr *actions,
         case OVS_ACTION_ATTR_CT:
         case OVS_ACTION_ATTR_PUSH_ETH:
         case OVS_ACTION_ATTR_POP_ETH:
-        case OVS_ACTION_ATTR_ENCAP_NSH:
-        case OVS_ACTION_ATTR_DECAP_NSH:
+        case OVS_ACTION_ATTR_PUSH_NSH:
+        case OVS_ACTION_ATTR_POP_NSH:
         case OVS_ACTION_ATTR_METER:
             ofpbuf_put(b, a, nl_attr_len_pad(a, left));
             break;
@@ -5804,17 +5804,17 @@  rewrite_flow_encap_ethernet(struct xlate_ctx *ctx,
 
 /* For an MD2 NSH header returns a pointer to an ofpbuf with the encoded
  * MD2 TLVs provided as encap properties to the encap operation. This
- * will be stored as encap_data in the ctx and copied into the encap_nsh
+ * will be stored as encap_data in the ctx and copied into the push_nsh
  * action at the next commit. */
 static struct ofpbuf *
-rewrite_flow_encap_nsh(struct xlate_ctx *ctx,
-                       const struct ofpact_encap *encap,
-                       struct flow *flow,
-                       struct flow_wildcards *wc)
+rewrite_flow_push_nsh(struct xlate_ctx *ctx,
+                      const struct ofpact_encap *encap,
+                      struct flow *flow,
+                      struct flow_wildcards *wc)
 {
     ovs_be32 packet_type = flow->packet_type;
     const char *ptr = (char *) encap->props;
-    struct ofpbuf *buf = ofpbuf_new(OVS_ENCAP_NSH_MAX_MD_LEN);
+    struct ofpbuf *buf = ofpbuf_new(OVS_PUSH_NSH_MAX_MD_LEN);
     uint8_t md_type = NSH_M_TYPE1;
     uint8_t np = 0;
     int i;
@@ -5854,7 +5854,7 @@  rewrite_flow_encap_nsh(struct xlate_ctx *ctx,
         }
         ptr += ROUND_UP(prop_ptr->len, 8);
     }
-    if (buf->size == 0 || buf->size > OVS_ENCAP_NSH_MAX_MD_LEN) {
+    if (buf->size == 0 || buf->size > OVS_PUSH_NSH_MAX_MD_LEN) {
         ofpbuf_delete(buf);
         buf = NULL;
     }
@@ -5899,7 +5899,7 @@  rewrite_flow_encap_nsh(struct xlate_ctx *ctx,
 
     if (md_type == NSH_M_TYPE1) {
         flow->nsh.mdtype = NSH_M_TYPE1;
-        memset(flow->nsh.c, 0, sizeof flow->nsh.c);
+        memset(flow->nsh.context, 0, sizeof flow->nsh.context);
         if (buf) {
             /* Drop any MD2 context TLVs. */
             ofpbuf_delete(buf);
@@ -5930,7 +5930,7 @@  xlate_generic_encap_action(struct xlate_ctx *ctx,
             rewrite_flow_encap_ethernet(ctx, flow, wc);
             break;
         case PT_NSH:
-            encap_data = rewrite_flow_encap_nsh(ctx, encap, flow, wc);
+            encap_data = rewrite_flow_push_nsh(ctx, encap, flow, wc);
             break;
         default:
             /* New packet type was checked during decoding. */
@@ -5973,7 +5973,7 @@  xlate_generic_decap_action(struct xlate_ctx *ctx,
             }
             return false;
         case PT_NSH:
-            /* The decap_nsh action is generated at the commit executed as
+            /* The pop_nsh action is generated at the commit executed as
              * part of freezing the ctx for recirculation. Here we just set
              * the new packet type based on the NSH next protocol field. */
             switch (flow->nsh.np) {
diff --git a/tests/nsh.at b/tests/nsh.at
index aa80a2a..562f3da 100644
--- a/tests/nsh.at
+++ b/tests/nsh.at
@@ -105,7 +105,7 @@  bridge("br0")
 
 Final flow: in_port=1,vlan_tci=0x0000,dl_src=00:00:00:00:00:00,dl_dst=11:22:33:44:55:66,dl_type=0x894f,nsh_flags=0,nsh_mdtype=1,nsh_np=3,nsh_spi=0x1234,nsh_si=255,nsh_c1=0x11223344,nsh_c2=0x0,nsh_c3=0x0,nsh_c4=0x0,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0
 Megaflow: recirc_id=0,eth,ip,in_port=1,dl_dst=66:77:88:99:aa:bb,nw_frag=no
-Datapath actions: encap_nsh(flags=0,mdtype=1,np=3,spi=0x1234,si=255,c1=0x11223344,c2=0x0,c3=0x0,c4=0x0),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),pop_eth,decap_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x1)
+Datapath actions: push_nsh(flags=0,mdtype=1,np=3,spi=0x1234,si=255,c1=0x11223344,c2=0x0,c3=0x0,c4=0x0),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),pop_eth,pop_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x1)
 ])
 
 AT_CHECK([
@@ -121,7 +121,7 @@  bridge("br0")
 
 Final flow: unchanged
 Megaflow: recirc_id=0,eth,in_port=4,dl_type=0x894f,nsh_mdtype=1,nsh_np=3,nsh_spi=0x1234,nsh_c1=0x11223344
-Datapath actions: pop_eth,decap_nsh(),recirc(0x2)
+Datapath actions: pop_eth,pop_nsh(),recirc(0x2)
 ])
 
 # Now send two real ICMP echo request packets in on port p1
@@ -139,7 +139,7 @@  ovs-appctl time/warp 1000
 AT_CHECK([
     ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v ipv6 | sort
 ], [0], [flow-dump from non-dpdk interfaces:
-recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(dst=1e:2c:e9:2a:66:9e),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:encap_nsh(flags=0,mdtype=1,np=3,spi=0x1234,si=255,c1=0x11223344,c2=0x0,c3=0x0,c4=0x0),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),pop_eth,decap_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x3)
+recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(dst=1e:2c:e9:2a:66:9e),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:push_nsh(flags=0,mdtype=1,np=3,spi=0x1234,si=255,c1=0x11223344,c2=0x0,c3=0x0,c4=0x0),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),pop_eth,pop_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x3)
 recirc_id(0x3),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:2
 ])
 
@@ -170,7 +170,7 @@  ovs-appctl time/warp 1000
 AT_CHECK([
     ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v ipv6 | sort
 ], [0], [flow-dump from non-dpdk interfaces:
-recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:push_vlan(vid=100,pcp=0),encap_nsh(flags=0,mdtype=1,np=3,spi=0x0,si=255,c1=0x0,c2=0x0,c3=0x0,c4=0x0),decap_nsh(),recirc(0x4)
+recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:push_vlan(vid=100,pcp=0),push_nsh(flags=0,mdtype=1,np=3,spi=0x0,si=255,c1=0x0,c2=0x0,c3=0x0,c4=0x0),pop_nsh(),recirc(0x4)
 recirc_id(0x4),in_port(1),packet_type(ns=0,id=0),eth_type(0x8100),vlan(vid=100,pcp=0),encap(eth_type(0x0800),ipv4(frag=no)), packets:1, bytes:102, used:0.0s, actions:2
 ])
 
@@ -195,7 +195,7 @@  ovs-vsctl set bridge br0 datapath_type=dummy \
         add-port br0 v4 -- set Interface v4 type=patch options:peer=v3 ofport_request=4])
 
 AT_DATA([flows.txt], [dnl
-    table=0,in_port=1,ip,actions=encap(nsh(md_type=2,tlv(0x1000,10,0x12345678))),set_field:0x1234->nsh_spi,encap(ethernet),set_field:11:22:33:44:55:66->dl_dst,3
+    table=0,in_port=1,ip,actions=encap(nsh(md_type=2,tlv(0x1000,10,0x12345678),tlv(0x2000,20,0xfedcba9876543210))),set_field:0x1234->nsh_spi,encap(ethernet),set_field:11:22:33:44:55:66->dl_dst,3
     table=0,in_port=4,dl_type=0x894f,nsh_mdtype=2,nsh_spi=0x1234,actions=decap(),decap(),2
 ])
 
@@ -205,7 +205,7 @@  AT_CHECK([
     ovs-ofctl -Oopenflow13 dump-flows br0 | ofctl_strip | sort | grep actions
 ], [0], [dnl
  in_port=4,dl_type=0x894f,nsh_mdtype=2,nsh_spi=0x1234 actions=decap(),decap(),output:2
- ip,in_port=1 actions=encap(nsh(md_type=2,tlv(0x1000,10,0x12345678))),set_field:0x1234->nsh_spi,encap(ethernet),set_field:11:22:33:44:55:66->eth_dst,output:3
+ ip,in_port=1 actions=encap(nsh(md_type=2,tlv(0x1000,10,0x12345678),tlv(0x2000,20,0xfedcba9876543210))),set_field:0x1234->nsh_spi,encap(ethernet),set_field:11:22:33:44:55:66->eth_dst,output:3
 ])
 
 AT_CHECK([
@@ -216,7 +216,7 @@  Flow: icmp,in_port=1,vlan_tci=0x0000,dl_src=00:11:22:33:44:55,dl_dst=66:77:88:99
 bridge("br0")
 -------------
  0. ip,in_port=1, priority 32768
-    encap(nsh(md_type=2,tlv(0x1000,10,0x12345678)))
+    encap(nsh(md_type=2,tlv(0x1000,10,0x12345678),tlv(0x2000,20,0xfedcba9876543210)))
     set_field:0x1234->nsh_spi
     encap(ethernet)
     set_field:11:22:33:44:55:66->eth_dst
@@ -230,7 +230,7 @@  bridge("br0")
 
 Final flow: in_port=1,vlan_tci=0x0000,dl_src=00:00:00:00:00:00,dl_dst=11:22:33:44:55:66,dl_type=0x894f,nsh_flags=0,nsh_mdtype=2,nsh_np=3,nsh_spi=0x1234,nsh_si=255,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0
 Megaflow: recirc_id=0,eth,ip,in_port=1,dl_dst=66:77:88:99:aa:bb,nw_frag=no
-Datapath actions: encap_nsh(flags=0,mdtype=2,np=3,spi=0x1234,si=255,md2=0x10000a0412345678),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),pop_eth,decap_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x1)
+Datapath actions: push_nsh(flags=0,mdtype=2,np=3,spi=0x1234,si=255,md2=0x10000a041234567820001408fedcba9876543210),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),pop_eth,pop_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x1)
 ])
 
 AT_CHECK([
@@ -246,7 +246,7 @@  bridge("br0")
 
 Final flow: unchanged
 Megaflow: recirc_id=0,eth,in_port=4,dl_type=0x894f,nsh_mdtype=2,nsh_np=3,nsh_spi=0x1234
-Datapath actions: pop_eth,decap_nsh(),recirc(0x2)
+Datapath actions: pop_eth,pop_nsh(),recirc(0x2)
 ])
 
 # Now send two real ICMP echo request packets in on port p1
@@ -264,7 +264,7 @@  ovs-appctl time/warp 1000
 AT_CHECK([
     ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v ipv6 | sort
 ], [0], [flow-dump from non-dpdk interfaces:
-recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(dst=1e:2c:e9:2a:66:9e),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:encap_nsh(flags=0,mdtype=2,np=3,spi=0x1234,si=255,md2=0x10000a0412345678),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),pop_eth,decap_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x3)
+recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(dst=1e:2c:e9:2a:66:9e),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:push_nsh(flags=0,mdtype=2,np=3,spi=0x1234,si=255,md2=0x10000a041234567820001408fedcba9876543210),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),pop_eth,pop_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x3)
 recirc_id(0x3),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:2
 ])
 
@@ -577,8 +577,8 @@  ovs-appctl time/warp 1000
 AT_CHECK([
     ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v ipv6 | sort
 ], [0], [flow-dump from non-dpdk interfaces:
-recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=192.168.10.30,frag=no), packets:1, bytes:98, used:0.0s, actions:pop_eth,encap_nsh(flags=0,mdtype=1,np=1,spi=0x3000,si=255,c1=0x0,c2=0x0,c3=0x0,c4=0x0),clone(tnl_push(tnl_port(4789),header(size=50,type=4,eth(dst=aa:55:00:00:00:03,src=aa:55:00:00:00:01,dl_type=0x0800),ipv4(src=10.0.0.1,dst=10.0.0.3,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,dst=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(1)),set(ipv4(src=30.0.0.1,dst=30.0.0.3)),tnl_pop(4789))
-tunnel(tun_id=0x0,src=30.0.0.1,dst=30.0.0.3,flags(-df-csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(np=1,spi=0x3000,si=255), packets:1, bytes:108, used:0.0s, actions:decap_nsh(),recirc(0x1)
+recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=192.168.10.30,frag=no), packets:1, bytes:98, used:0.0s, actions:pop_eth,push_nsh(flags=0,mdtype=1,np=1,spi=0x3000,si=255,c1=0x0,c2=0x0,c3=0x0,c4=0x0),clone(tnl_push(tnl_port(4789),header(size=50,type=4,eth(dst=aa:55:00:00:00:03,src=aa:55:00:00:00:01,dl_type=0x0800),ipv4(src=10.0.0.1,dst=10.0.0.3,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,dst=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(1)),set(ipv4(src=30.0.0.1,dst=30.0.0.3)),tnl_pop(4789))
+tunnel(tun_id=0x0,src=30.0.0.1,dst=30.0.0.3,flags(-df-csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(np=1,spi=0x3000,si=255), packets:1, bytes:108, used:0.0s, actions:pop_nsh(),recirc(0x1)
 tunnel(tun_id=0x0,src=30.0.0.1,dst=30.0.0.3,flags(-df-csum+key)),recirc_id(0x1),in_port(4789),packet_type(ns=1,id=0x800),ipv4(frag=no), packets:1, bytes:84, used:0.0s, actions:push_eth(src=00:00:00:00:00:00,dst=aa:55:aa:55:00:03),6
 ])
 
@@ -631,9 +631,9 @@  ovs-appctl time/warp 1000
 AT_CHECK([
     ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v ipv6 | sort
 ], [0], [flow-dump from non-dpdk interfaces:
-recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=192.168.10.20/255.255.255.248,frag=no), packets:1, bytes:98, used:0.0s, actions:pop_eth,encap_nsh(flags=0,mdtype=1,np=1,spi=0x3020,si=255,c1=0x0,c2=0x0,c3=0x0,c4=0x0),clone(tnl_push(tnl_port(4789),header(size=50,type=4,eth(dst=aa:55:00:00:00:02,src=aa:55:00:00:00:01,dl_type=0x0800),ipv4(src=10.0.0.1,dst=10.0.0.2,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,dst=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(1)),set(ipv4(src=20.0.0.1,dst=20.0.0.2)),tnl_pop(4789))
+recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=192.168.10.20/255.255.255.248,frag=no), packets:1, bytes:98, used:0.0s, actions:pop_eth,push_nsh(flags=0,mdtype=1,np=1,spi=0x3020,si=255,c1=0x0,c2=0x0,c3=0x0,c4=0x0),clone(tnl_push(tnl_port(4789),header(size=50,type=4,eth(dst=aa:55:00:00:00:02,src=aa:55:00:00:00:01,dl_type=0x0800),ipv4(src=10.0.0.1,dst=10.0.0.2,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,dst=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(1)),set(ipv4(src=20.0.0.1,dst=20.0.0.2)),tnl_pop(4789))
 tunnel(tun_id=0x0,src=20.0.0.1,dst=20.0.0.2,flags(-df-csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(spi=0x3020,si=255), packets:1, bytes:108, used:0.0s, actions:push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),set(nsh(spi=0x3020,si=254)),pop_eth,clone(tnl_push(tnl_port(4789),header(size=50,type=4,eth(dst=aa:55:00:00:00:03,src=aa:55:00:00:00:02,dl_type=0x0800),ipv4(src=20.0.0.2,dst=20.0.0.3,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,dst=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(2)),set(ipv4(src=30.0.0.2,dst=30.0.0.3)),tnl_pop(4789))
-tunnel(tun_id=0x0,src=30.0.0.2,dst=30.0.0.3,flags(-df-csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(np=1,spi=0x3020,si=254), packets:1, bytes:108, used:0.0s, actions:decap_nsh(),recirc(0x2)
+tunnel(tun_id=0x0,src=30.0.0.2,dst=30.0.0.3,flags(-df-csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(np=1,spi=0x3020,si=254), packets:1, bytes:108, used:0.0s, actions:pop_nsh(),recirc(0x2)
 tunnel(tun_id=0x0,src=30.0.0.2,dst=30.0.0.3,flags(-df-csum+key)),recirc_id(0x2),in_port(4789),packet_type(ns=1,id=0x800),ipv4(frag=no), packets:1, bytes:84, used:0.0s, actions:push_eth(src=00:00:00:00:00:00,dst=aa:55:aa:55:00:03),6
 ])