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

Yi Yang yi.y.yang at intel.com
Sat Jan 6 05:47:51 UTC 2018


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

Its intention is to align to NSH kernel implementation.

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 at intel.com>
---
 datapath/linux/compat/include/linux/openvswitch.h |  58 +-
 include/openvswitch/nsh.h                         |  26 +-
 include/openvswitch/packets.h                     |  11 +-
 lib/dpif-netdev.c                                 |   4 +-
 lib/dpif.c                                        |   4 +-
 lib/flow.c                                        |  40 +-
 lib/match.c                                       |  12 +-
 lib/meta-flow.c                                   |  13 +-
 lib/nx-match.c                                    |   4 +-
 lib/odp-execute.c                                 |  77 ++-
 lib/odp-util.c                                    | 726 ++++++++++++++++++----
 lib/odp-util.h                                    |   4 +
 lib/packets.c                                     |  26 +-
 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                                      |  30 +-
 18 files changed, 786 insertions(+), 286 deletions(-)

diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h
index 561f895..3ddb1c5 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,28 @@ 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_UNSPEC,
+	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,25 +808,6 @@ struct ovs_action_push_eth {
 	struct ovs_key_ethernet addresses;
 };
 
-#define OVS_ENCAP_NSH_MAX_MD_LEN 248
-/*
- * 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, including padding.
- * @np: NSH next_protocol: Inner packet type.
- * @path_hdr: NSH service path id and service index.
- * @metadata: NSH context metadata, padded to 4-bytes
- */
-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];
-};
-
 /**
  * enum ovs_nat_attr - Attributes for %OVS_CT_ATTR_NAT.
  *
@@ -887,8 +883,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 +926,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 a58c870..63b480e 100644
--- a/include/openvswitch/nsh.h
+++ b/include/openvswitch/nsh.h
@@ -199,7 +199,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 {
@@ -262,6 +262,12 @@ struct nsh_hdr {
 /* NSH MD Type 1 header Length. */
 #define NSH_M_TYPE1_LEN   24
 
+/* NSH header maximum Length. */
+#define NSH_HDR_MAX_LEN 256
+
+/* NSH context headers maximum Length. */
+#define NSH_CTX_HDRS_MAX_LEN 248
+
 static inline uint16_t
 nsh_hdr_len(const struct nsh_hdr *nsh)
 {
@@ -275,16 +281,22 @@ nsh_md_type(const struct nsh_hdr *nsh)
     return (nsh->md_type & NSH_MDTYPE_MASK) >> NSH_MDTYPE_SHIFT;
 }
 
-static inline struct nsh_md1_ctx *
-nsh_md1_ctx(struct nsh_hdr *nsh)
+static inline uint8_t
+nsh_get_ver(const struct nsh_hdr *nsh)
+{
+    return (ntohs(nsh->ver_flags_ttl_len) & NSH_VER_MASK) >> NSH_VER_SHIFT;
+}
+
+static inline uint8_t
+nsh_get_flags(const struct nsh_hdr *nsh)
 {
-    return &nsh->md1;
+    return (ntohs(nsh->ver_flags_ttl_len) & NSH_FLAGS_MASK) >> NSH_FLAGS_SHIFT;
 }
 
-static inline struct nsh_md2_tlv *
-nsh_md2_ctx(struct nsh_hdr *nsh)
+static inline void
+nsh_reset_ver_flags_ttl_len(struct nsh_hdr *nsh)
 {
-    return &nsh->md2;
+    nsh->ver_flags_ttl_len = 0;
 }
 
 #ifdef  __cplusplus
diff --git a/include/openvswitch/packets.h b/include/openvswitch/packets.h
index 43b93b3..ae1cf9c 100644
--- a/include/openvswitch/packets.h
+++ b/include/openvswitch/packets.h
@@ -81,7 +81,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 349e98a..d06f585 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -5760,8 +5760,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 310dec1..ab2e232 100644
--- a/lib/dpif.c
+++ b/lib/dpif.c
@@ -1273,8 +1273,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 bc24fe7..964f734 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -533,7 +533,6 @@ 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;
 
@@ -544,16 +543,11 @@ parse_nsh(const void **datap, size_t *sizep, struct flow_nsh *key)
         return false;
     }
 
-    memset(key, 0, sizeof(struct flow_nsh));
+    version = nsh_get_ver(nsh);
+    flags = nsh_get_flags(nsh);
+    length = nsh_hdr_len(nsh);
 
-    ver_flags_len = ntohs(nsh->ver_flags_ttl_len);
-    version = (ver_flags_len & NSH_VER_MASK) >> NSH_VER_SHIFT;
-    flags = (ver_flags_len & NSH_FLAGS_MASK) >> NSH_FLAGS_SHIFT;
-
-    /* NSH header length is in 4 byte words. */
-    length = ((ver_flags_len & NSH_LEN_MASK) >> NSH_LEN_SHIFT) << 2;
-
-    if (length > *sizep || version != 0) {
+    if (OVS_UNLIKELY(length > *sizep || version != 0)) {
         return false;
     }
 
@@ -571,10 +565,17 @@ parse_nsh(const void **datap, size_t *sizep, struct flow_nsh *key)
                 return false;
             }
             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 metedata parsing yet */
+            if (length < NSH_BASE_HDR_LEN) {
+                return false;
+            }
+
+            memset(key->context, 0, sizeof(key->context));
+            break;
         default:
             /* We don't parse other context headers yet. */
             break;
@@ -878,16 +879,9 @@ miniflow_extract(struct dp_packet *packet, struct miniflow *dst)
             struct flow_nsh nsh;
 
             if (OVS_LIKELY(parse_nsh(&data, &size, &nsh))) {
-                if (nsh.mdtype == NSH_M_TYPE1) {
-                    miniflow_push_words(mf, nsh, &nsh,
-                                        sizeof(struct flow_nsh) /
-                                        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
-                     */
-                }
+                miniflow_push_words(mf, nsh, &nsh,
+                                    sizeof(struct flow_nsh) /
+                                    sizeof(uint64_t));
             }
         }
         goto out;
@@ -1694,7 +1688,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. */
     }
@@ -1828,7 +1822,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 56e316e..7f9d419 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -274,20 +274,23 @@ 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);
     uint8_t mdtype = nsh_md_type(nsh);
+    ovs_be32 path_hdr;
 
     if (!mask) {
         nsh->ver_flags_ttl_len = htons(key->flags << NSH_FLAGS_SHIFT) |
                 (nsh->ver_flags_ttl_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 (mdtype) {
             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:
@@ -302,16 +305,24 @@ odp_set_nsh(struct dp_packet *packet, const struct ovs_key_nsh *key,
         nsh->ver_flags_ttl_len = htons(flags << NSH_FLAGS_SHIFT) |
                 (nsh->ver_flags_ttl_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 (mdtype) {
             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:
@@ -347,9 +358,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));
@@ -475,10 +489,26 @@ 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;
+        struct {
+            struct nlattr nla;
+            uint8_t data[sizeof(struct ovs_nsh_key_base) + NSH_CTX_HDRS_MAX_LEN
+                         + 2 * NLA_HDRLEN];
+        } attr, mask;
+        size_t size = nl_attr_get_size(a) / 2;
+
+        mask.nla.nla_type = attr.nla.nla_type = nl_attr_type(a);
+        mask.nla.nla_len = attr.nla.nla_len = NLA_HDRLEN + size;
+        memcpy(attr.data, (char *)(a + 1), size);
+        memcpy(mask.data, (char *)(a + 1) + size, size);
+
+        odp_nsh_key_from_attr(&attr.nla, &nsh);
+        odp_nsh_key_from_attr(&mask.nla, &nsh_mask);
+        odp_set_nsh(packet, &nsh, &nsh_mask);
+
         break;
+    }
 
     case OVS_KEY_ATTR_IPV4:
         odp_set_ipv4(packet, nl_attr_get(a),
@@ -654,8 +684,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:
@@ -833,19 +863,22 @@ 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: {
+            uint32_t buffer[NSH_HDR_MAX_LEN / 4];
+            struct nsh_hdr *nsh_hdr = ALIGNED_CAST(struct nsh_hdr *, buffer);
+            nsh_reset_ver_flags_ttl_len(nsh_hdr);
+            odp_nsh_hdr_from_attr(nl_attr_get(a), nsh_hdr, NSH_HDR_MAX_LEN);
             DP_PACKET_BATCH_FOR_EACH (packet, batch) {
-                encap_nsh(packet, enc_nsh);
+                push_nsh(packet, nsh_hdr);
             }
             break;
         }
-        case OVS_ACTION_ATTR_DECAP_NSH: {
+        case OVS_ACTION_ATTR_POP_NSH: {
             size_t i;
             const size_t num = dp_packet_batch_size(batch);
 
             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 94edd78..ff08821 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -121,8 +121,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:
@@ -256,7 +256,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:
@@ -326,41 +326,48 @@ 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 = nsh_hdr_len(nsh_hdr) - 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 = nsh_get_flags(nsh_hdr);
 
-    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_hdr->md1;
         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_hdr->md2;
         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();
     }
@@ -1049,11 +1056,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: {
+        uint32_t buffer[NSH_HDR_MAX_LEN / 4];
+        struct nsh_hdr *nsh_hdr = ALIGNED_CAST(struct nsh_hdr *, buffer);
+        nsh_reset_ver_flags_ttl_len(nsh_hdr);
+        odp_nsh_hdr_from_attr(nl_attr_get(a), nsh_hdr, NSH_HDR_MAX_LEN);
+        format_odp_push_nsh_action(ds, nsh_hdr);
         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:
@@ -1772,27 +1784,70 @@ 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;
+
+    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) {
+        nl_msg_put_unspec(buf, OVS_NSH_KEY_ATTR_MD1, nsh->context,
+                          sizeof nsh->context);
+    } else {
+        switch (nsh->mdtype) {
+        case NSH_M_TYPE1:
+            nl_msg_put_unspec(buf, OVS_NSH_KEY_ATTR_MD1, nsh->context,
+                              sizeof nsh->context);
+            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 spi = 0;
     uint32_t cd;
+    struct flow_nsh nsh;
+    uint8_t metadata[NSH_CTX_HDRS_MAX_LEN];
+    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);
@@ -1800,17 +1855,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;
@@ -1818,55 +1873,48 @@ 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));
+            nsh.spi = htonl(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, padding;
             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);
+                ofpbuf_use_stub(&b, metadata,
+                                NSH_CTX_HDRS_MAX_LEN);
                 ofpbuf_put_hex(&b, buf, &mdlen);
                 /* Pad metadata to 4 bytes. */
                 padding = PAD_SIZE(mdlen, 4);
                 if (padding > 0) {
                     ofpbuf_push_zeros(&b, padding);
                 }
-                encap_nsh.mdlen = mdlen + padding;
+                md_size = mdlen + padding;
                 ofpbuf_uninit(&b);
                 continue;
             }
@@ -1876,15 +1924,13 @@ parse_odp_encap_nsh_action(const char *s, struct ofpbuf *actions)
         goto out;
     }
 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;
     }
+    return ret;
 }
 
 static int
@@ -2098,8 +2144,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;
             }
@@ -2109,8 +2155,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;
         }
     }
@@ -2207,6 +2253,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 },
+};
+
 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 },
@@ -2238,7 +2291,9 @@ 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
@@ -2289,6 +2344,139 @@ 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, size_t size)
+{
+    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_hdr->md1;
+            has_md1 = true;
+            mdlen = nl_attr_get_size(a);
+            if ((mdlen + NSH_BASE_HDR_LEN != NSH_M_TYPE1_LEN) ||
+                (mdlen + NSH_BASE_HDR_LEN > size)) {
+                return ODP_FIT_ERROR;
+            }
+            memcpy(md1_dst, md1, mdlen);
+            break;
+        }
+        case OVS_NSH_KEY_ATTR_MD2: {
+            struct nsh_md2_tlv *md2_dst = &nsh_hdr->md2;
+            const uint8_t *md2 = nl_attr_get(a);
+            has_md2 = true;
+            mdlen = nl_attr_get_size(a);
+            if (mdlen + NSH_BASE_HDR_LEN > size) {
+                return ODP_FIT_ERROR;
+            }
+            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_ttl_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;
+            memcpy(nsh->context, md1->context, sizeof md1->context);
+            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)
@@ -2980,6 +3168,77 @@ 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_UNSPEC:
+            break;
+        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;
+            memcpy(nsh.context, md1->context, sizeof md1->context);
+            if (md1_mask) {
+                memcpy(nsh_mask.context, md1_mask->context,
+                       sizeof md1_mask->context);
+            }
+            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)
 {
@@ -3457,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:
@@ -4559,6 +4816,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)
 {
@@ -4704,16 +5084,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)) {
@@ -5004,11 +5381,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)) {
@@ -5568,13 +5941,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;
             }
         }
@@ -6630,13 +7000,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:
@@ -6660,17 +7030,124 @@ 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:
+            memcpy(md1.context, key->context, sizeof key->context);
+            memcpy(md1_mask.context, mask->context, sizeof mask->context);
+
+            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,
@@ -6694,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);
@@ -6798,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 < NSH_CTX_HDRS_MAX_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
@@ -6867,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,
@@ -6893,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 72a3c30..f7ce206 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 *, size_t size);
 
 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 39bf811..8ebae8c 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -415,10 +415,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)) {
@@ -439,33 +439,15 @@ 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_ttl_len =
-            htons(((encap->flags << NSH_FLAGS_SHIFT) & NSH_FLAGS_MASK)
-                    | (63 << NSH_TTL_SHIFT)
-                    | ((length >> 2) << NSH_LEN_SHIFT));
-    nsh->md_type = (encap->mdtype << NSH_MDTYPE_SHIFT) & NSH_MDTYPE_MASK;
+    memcpy(nsh, nsh_hdr_src, length);
     nsh->next_proto = next_proto;
-    put_16aligned_be32(&nsh->path_hdr, encap->path_hdr);
-    switch (encap->mdtype) {
-        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. */
-            memcpy(&nsh->md2, encap->metadata, encap->mdlen);
-            break;
-        }
-        default:
-            OVS_NOT_REACHED();
-    }
-
     packet->packet_type = htonl(PT_NSH);
     dp_packet_reset_offsets(packet);
     packet->l3_ofs = 0;
 }
 
 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 395599f..f583e05 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 4d16878..2a43537 100644
--- a/ofproto/ofproto-dpif-ipfix.c
+++ b/ofproto/ofproto-dpif-ipfix.c
@@ -2822,8 +2822,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 ccf8964..2be5869 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 d94e9dc..4506150 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -4387,8 +4387,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;
@@ -5838,17 +5838,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(NSH_CTX_HDRS_MAX_LEN);
     uint8_t md_type = NSH_M_TYPE1;
     uint8_t np = 0;
     int i;
@@ -5888,7 +5888,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 > NSH_CTX_HDRS_MAX_LEN) {
         ofpbuf_delete(buf);
         buf = NULL;
     }
@@ -5933,7 +5933,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);
@@ -5964,7 +5964,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. */
@@ -6006,7 +6006,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..bec6e87 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 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),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
 ])
 
@@ -166,11 +166,13 @@ AT_CHECK([
 ], [0], [ignore])
 
 ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+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=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 +197,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 +207,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 +218,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 +232,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 +248,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 +266,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),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 +579,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=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 +633,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=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
 ])
 
-- 
2.1.0



More information about the dev mailing list