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

Jan Scheurich jan.scheurich at ericsson.com
Fri Aug 18 15:02:35 UTC 2017


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 at 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 at 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 at 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 at 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 at 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 at 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




More information about the dev mailing list