diff mbox series

[ovs-dev,v7,2/4] nsh: add new flow key 'ttl'

Message ID 1515217674-66361-3-git-send-email-yi.y.yang@intel.com
State Changes Requested
Headers show
Series nsh: add new nsh key ttl and action dec_nsh_ttl | expand

Commit Message

Yang, Yi Jan. 6, 2018, 5:47 a.m. UTC
IETF NSH draft added a new filed ttl in NSH header, this patch
is to add new nsh key 'ttl' for it.

Signed-off-by: Yi Yang <yi.y.yang@intel.com>
---
 datapath/linux/compat/include/linux/openvswitch.h |   2 +-
 include/openvswitch/flow.h                        |   6 +-
 include/openvswitch/meta-flow.h                   |  31 +++--
 include/openvswitch/nsh.h                         |  96 ++++++++++++++
 include/openvswitch/packets.h                     |  12 +-
 lib/flow.c                                        |  23 ++--
 lib/flow.h                                        |   2 +-
 lib/match.c                                       |  12 +-
 lib/meta-flow.c                                   |  56 ++++++--
 lib/meta-flow.xml                                 |   6 +-
 lib/nx-match.c                                    |  16 ++-
 lib/odp-execute.c                                 |  40 +++---
 lib/odp-util.c                                    | 151 ++++++++--------------
 lib/odp-util.h                                    |   2 +-
 lib/packets.c                                     |   1 +
 ofproto/ofproto-dpif-xlate.c                      |   7 +-
 tests/nsh.at                                      |  41 +++---
 17 files changed, 308 insertions(+), 196 deletions(-)

Comments

Ben Pfaff Jan. 9, 2018, 12:11 a.m. UTC | #1
On Sat, Jan 06, 2018 at 01:47:52PM +0800, Yi Yang wrote:
> IETF NSH draft added a new filed ttl in NSH header, this patch
> is to add new nsh key 'ttl' for it.
> 
> Signed-off-by: Yi Yang <yi.y.yang@intel.com>

Thanks for v7!

The field assignments in meta-flow.h seem wrong to me:

        - The TTL field is new in v2.9, so it shouldn't say v2.8.

        - The existing fields should not be renumbered because that
          breaks OpenFlow wire format compatibility with anything that
          already knows how to talk to OVS 2.8.  Please keep the
          existing numbering.

Why does nsh_16aligned_be32 exist?  Please use get_16aligned_be32, which
is identical.

In meta-flow.xml, please properly document the NSH fields, following the
pattern set by the other documentation in the file.

I see several uses of memcpy() for copying a struct.  Please use an
assignment to copy structs.

This statement looks suspicious, since the target and the "sizeof" are
different:
        memset(nsh, 0, sizeof(nsh->context));

I'm concerned about how this patch introduces different structs with
identical layouts and then uses memcpy() to copy between them.  This is
a trap for unsuspecting developers who change one structure or the other
(even by reordering fields).  It would probably be better to figure out
a way to either use the same struct in each case, or to do
member-by-member copies.  Another way would be to use assertions to make
sure that the structures really are identical.

Thanks,

Ben.
Yang, Yi Jan. 9, 2018, 2:04 p.m. UTC | #2
On Tue, Jan 09, 2018 at 08:11:15AM +0800, Ben Pfaff wrote:
> On Sat, Jan 06, 2018 at 01:47:52PM +0800, Yi Yang wrote:
> > IETF NSH draft added a new filed ttl in NSH header, this patch
> > is to add new nsh key 'ttl' for it.
> > 
> > Signed-off-by: Yi Yang <yi.y.yang@intel.com>
> 
> Thanks for v7!
> 
> The field assignments in meta-flow.h seem wrong to me:
> 
>         - The TTL field is new in v2.9, so it shouldn't say v2.8.
> 
>         - The existing fields should not be renumbered because that
>           breaks OpenFlow wire format compatibility with anything that
>           already knows how to talk to OVS 2.8.  Please keep the
>           existing numbering.
> 
> Why does nsh_16aligned_be32 exist?  Please use get_16aligned_be32, which
> is identical.

I want to keep include/openvswitch/nsh.h consistent as possible as nsh.h in
kernel, it is not good to depend on lib/unaligned.h because it is only used
in lib/*, I have fixed sparse warnings.

> 
> In meta-flow.xml, please properly document the NSH fields, following the
> pattern set by the other documentation in the file.
> 
> I see several uses of memcpy() for copying a struct.  Please use an
> assignment to copy structs.
> 
> This statement looks suspicious, since the target and the "sizeof" are
> different:
>         memset(nsh, 0, sizeof(nsh->context));
> 
> I'm concerned about how this patch introduces different structs with
> identical layouts and then uses memcpy() to copy between them.  This is
> a trap for unsuspecting developers who change one structure or the other
> (even by reordering fields).  It would probably be better to figure out
> a way to either use the same struct in each case, or to do
> member-by-member copies.  Another way would be to use assertions to make
> sure that the structures really are identical.

Your concerns make sense, I use assignments for them. v8 has been
posted, please review new ones. Thanks a lot.

> 
> Thanks,
> 
> Ben.
diff mbox series

Patch

diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h
index 3ddb1c5..c7142b6 100644
--- a/datapath/linux/compat/include/linux/openvswitch.h
+++ b/datapath/linux/compat/include/linux/openvswitch.h
@@ -504,9 +504,9 @@  enum ovs_nsh_key_attr {
 
 struct ovs_nsh_key_base {
 	__u8 flags;
+	__u8 ttl;
 	__u8 mdtype;
 	__u8 np;
-	__u8 pad;
 	__be32 path_hdr;
 };
 
diff --git a/include/openvswitch/flow.h b/include/openvswitch/flow.h
index a658a58..cd61fff 100644
--- a/include/openvswitch/flow.h
+++ b/include/openvswitch/flow.h
@@ -146,7 +146,7 @@  struct flow {
     struct eth_addr arp_tha;    /* ARP/ND target hardware address. */
     ovs_be16 tcp_flags;         /* TCP flags. With L3 to avoid matching L4. */
     ovs_be16 pad2;              /* Pad to 64 bits. */
-    struct flow_nsh nsh;        /* Network Service Header keys */
+    struct ovs_key_nsh nsh;     /* Network Service Header keys */
 
     /* L4 (64-bit aligned) */
     ovs_be16 tp_src;            /* TCP/UDP/SCTP source port/ICMP type. */
@@ -159,13 +159,13 @@  struct flow {
 };
 BUILD_ASSERT_DECL(sizeof(struct flow) % sizeof(uint64_t) == 0);
 BUILD_ASSERT_DECL(sizeof(struct flow_tnl) % sizeof(uint64_t) == 0);
-BUILD_ASSERT_DECL(sizeof(struct flow_nsh) % sizeof(uint64_t) == 0);
+BUILD_ASSERT_DECL(sizeof(struct ovs_key_nsh) % sizeof(uint64_t) == 0);
 
 #define FLOW_U64S (sizeof(struct flow) / sizeof(uint64_t))
 
 /* Remember to update FLOW_WC_SEQ when changing 'struct flow'. */
 BUILD_ASSERT_DECL(offsetof(struct flow, igmp_group_ip4) + sizeof(uint32_t)
-                  == sizeof(struct flow_tnl) + sizeof(struct flow_nsh) + 300
+                  == sizeof(struct flow_tnl) + sizeof(struct ovs_key_nsh) + 300
                   && FLOW_WC_SEQ == 40);
 
 /* Incremental points at which flow classification may be performed in
diff --git a/include/openvswitch/meta-flow.h b/include/openvswitch/meta-flow.h
index 436501f..14e6b59 100644
--- a/include/openvswitch/meta-flow.h
+++ b/include/openvswitch/meta-flow.h
@@ -1757,6 +1757,21 @@  enum OVS_PACKED_ENUM mf_field_id {
      */
     MFF_NSH_FLAGS,
 
+    /* "nsh_ttl".
+     *
+     * TTL field in NSH base header.
+     *
+     * Type: u8.
+     * Maskable: no.
+     * Formatting: decimal.
+     * Prerequisites: NSH.
+     * Access: read/write.
+     * NXM: none.
+     * OXM: NXOXM_NSH_TTL(2) since OF1.3 and v2.8.
+     */
+    MFF_NSH_TTL,
+
+
     /* "nsh_mdtype".
      *
      * mdtype field in NSH base header.
@@ -1767,7 +1782,7 @@  enum OVS_PACKED_ENUM mf_field_id {
      * Prerequisites: NSH.
      * Access: read-only.
      * NXM: none.
-     * OXM: NXOXM_NSH_MDTYPE(2) since OF1.3 and v2.8.
+     * OXM: NXOXM_NSH_MDTYPE(3) since OF1.3 and v2.8.
      */
     MFF_NSH_MDTYPE,
 
@@ -1781,7 +1796,7 @@  enum OVS_PACKED_ENUM mf_field_id {
      * Prerequisites: NSH.
      * Access: read-only.
      * NXM: none.
-     * OXM: NXOXM_NSH_NP(3) since OF1.3 and v2.8.
+     * OXM: NXOXM_NSH_NP(4) since OF1.3 and v2.8.
      */
     MFF_NSH_NP,
 
@@ -1795,7 +1810,7 @@  enum OVS_PACKED_ENUM mf_field_id {
      * Prerequisites: NSH.
      * Access: read/write.
      * NXM: none.
-     * OXM: NXOXM_NSH_SPI(4) since OF1.3 and v2.8.
+     * OXM: NXOXM_NSH_SPI(5) since OF1.3 and v2.8.
      */
     MFF_NSH_SPI,
 
@@ -1809,7 +1824,7 @@  enum OVS_PACKED_ENUM mf_field_id {
      * Prerequisites: NSH.
      * Access: read/write.
      * NXM: none.
-     * OXM: NXOXM_NSH_SI(5) since OF1.3 and v2.8.
+     * OXM: NXOXM_NSH_SI(6) since OF1.3 and v2.8.
      */
     MFF_NSH_SI,
 
@@ -1823,10 +1838,10 @@  enum OVS_PACKED_ENUM mf_field_id {
      * Prerequisites: NSH.
      * Access: read/write.
      * NXM: none.
-     * OXM: NXOXM_NSH_C1(6) since OF1.3 and v2.8.        <1>
-     * OXM: NXOXM_NSH_C2(7) since OF1.3 and v2.8.        <2>
-     * OXM: NXOXM_NSH_C3(8) since OF1.3 and v2.8.        <3>
-     * OXM: NXOXM_NSH_C4(9) since OF1.3 and v2.8.        <4>
+     * OXM: NXOXM_NSH_C1(7) since OF1.3 and v2.8.        <1>
+     * OXM: NXOXM_NSH_C2(8) since OF1.3 and v2.8.        <2>
+     * OXM: NXOXM_NSH_C3(9) since OF1.3 and v2.8.        <3>
+     * OXM: NXOXM_NSH_C4(10) since OF1.3 and v2.8.        <4>
      */
     MFF_NSH_C1,
     MFF_NSH_C2,
diff --git a/include/openvswitch/nsh.h b/include/openvswitch/nsh.h
index 63b480e..9b02146 100644
--- a/include/openvswitch/nsh.h
+++ b/include/openvswitch/nsh.h
@@ -299,6 +299,102 @@  nsh_reset_ver_flags_ttl_len(struct nsh_hdr *nsh)
     nsh->ver_flags_ttl_len = 0;
 }
 
+static inline uint8_t
+nsh_get_ttl(const struct nsh_hdr *nsh)
+{
+    return (ntohs(nsh->ver_flags_ttl_len) & NSH_TTL_MASK) >> NSH_TTL_SHIFT;
+}
+
+static inline ovs_be32
+nsh_16aligned_be32(const ovs_16aligned_be32 *x)
+{
+#ifdef WORDS_BIGENDIAN
+    return ((ovs_be32) x->hi << 16) | (ovs_be32) x->lo;
+#else
+    return ((ovs_be32) x->lo << 16) | (ovs_be32) x->hi;
+#endif
+}
+
+static inline ovs_be32
+nsh_get_path_hdr(const struct nsh_hdr *nsh)
+{
+    return nsh_16aligned_be32(&nsh->path_hdr);
+}
+
+static inline ovs_be32
+nsh_get_spi(const struct nsh_hdr *nsh)
+{
+    ovs_be32 path_hdr = ntohl(nsh_16aligned_be32(&nsh->path_hdr));
+    return htonl((path_hdr & NSH_SPI_MASK) >> NSH_SPI_SHIFT);
+}
+
+static inline uint8_t
+nsh_get_si(const struct nsh_hdr *nsh)
+{
+    ovs_be32 path_hdr = ntohl(nsh_16aligned_be32(&nsh->path_hdr));
+    return (path_hdr & NSH_SI_MASK) >> NSH_SI_SHIFT;
+}
+
+static inline ovs_be32
+nsh_path_hdr_to_spi(ovs_be32 path_hdr)
+{
+    return htonl((ntohl(path_hdr) & NSH_SPI_MASK) >> NSH_SPI_SHIFT);
+}
+
+static inline uint32_t
+nsh_path_hdr_to_spi_uint32(ovs_be32 path_hdr)
+{
+    return (ntohl(path_hdr) & NSH_SPI_MASK) >> NSH_SPI_SHIFT;
+}
+
+static inline uint8_t
+nsh_path_hdr_to_si(ovs_be32 path_hdr)
+{
+    return (ntohl(path_hdr) & NSH_SI_MASK) >> NSH_SI_SHIFT;
+}
+
+static inline ovs_be32
+nsh_spi_si_to_path_hdr(uint32_t spi, uint8_t si)
+{
+    return htonl((spi << NSH_SPI_SHIFT) | si);
+}
+
+static inline void
+nsh_set_flags_and_ttl(struct nsh_hdr *nsh, uint8_t flags, uint8_t ttl)
+{
+    nsh->ver_flags_ttl_len
+        = htons((ntohs(nsh->ver_flags_ttl_len)
+                 & ~(NSH_FLAGS_MASK | NSH_TTL_MASK))
+                | ((flags << NSH_FLAGS_SHIFT)& NSH_FLAGS_MASK)
+                | ((ttl << NSH_TTL_SHIFT) & NSH_TTL_MASK));
+}
+
+static inline void
+nsh_set_flags_ttl_len(struct nsh_hdr *nsh, uint8_t flags, uint8_t ttl,
+                      uint16_t len)
+{
+    nsh->ver_flags_ttl_len
+        = htons((ntohs(nsh->ver_flags_ttl_len)
+                 & ~(NSH_FLAGS_MASK | NSH_TTL_MASK | NSH_LEN_MASK))
+                | ((flags << NSH_FLAGS_SHIFT)& NSH_FLAGS_MASK)
+                | ((ttl << NSH_TTL_SHIFT) & NSH_TTL_MASK)
+                | (((len >> 2) << NSH_LEN_SHIFT) & NSH_LEN_MASK));
+}
+
+static inline void
+nsh_path_hdr_set_spi(ovs_be32 *path_hdr, ovs_be32 spi)
+{
+    *path_hdr = htonl((ntohl(*path_hdr) & ~NSH_SPI_MASK) |
+                      ((ntohl(spi) << NSH_SPI_SHIFT) & NSH_SPI_MASK));
+}
+
+static inline void
+nsh_path_hdr_set_si(ovs_be32 *path_hdr, uint8_t si)
+{
+    *path_hdr = htonl((ntohl(*path_hdr) & ~NSH_SI_MASK) |
+                      ((si << NSH_SI_SHIFT) & NSH_SI_MASK));
+}
+
 #ifdef  __cplusplus
 }
 #endif
diff --git a/include/openvswitch/packets.h b/include/openvswitch/packets.h
index ae1cf9c..fef756b 100644
--- a/include/openvswitch/packets.h
+++ b/include/openvswitch/packets.h
@@ -74,21 +74,11 @@  union flow_vlan_hdr {
     };
 };
 
-/* Network Service Header keys */
-struct flow_nsh {
-    uint8_t flags;
-    uint8_t mdtype;
-    uint8_t np;
-    uint8_t si;
-    ovs_be32 spi;
-    ovs_be32 context[4];
-};
-
 struct ovs_key_nsh {
     uint8_t flags;
+    uint8_t ttl;
     uint8_t mdtype;
     uint8_t np;
-    uint8_t pad;
     ovs_be32 path_hdr;
     ovs_be32 context[4];
 };
diff --git a/lib/flow.c b/lib/flow.c
index 964f734..f9d7c2a 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -530,11 +530,10 @@  parse_ipv6_ext_hdrs(const void **datap, size_t *sizep, uint8_t *nw_proto,
 }
 
 bool
-parse_nsh(const void **datap, size_t *sizep, struct flow_nsh *key)
+parse_nsh(const void **datap, size_t *sizep, struct ovs_key_nsh *key)
 {
     const struct nsh_hdr *nsh = (const struct nsh_hdr *) *datap;
-    uint8_t version, length, flags;
-    uint32_t path_hdr;
+    uint8_t version, length, flags, ttl;
 
     /* Check if it is long enough for NSH header, doesn't support
      * MD type 2 yet
@@ -546,18 +545,17 @@  parse_nsh(const void **datap, size_t *sizep, struct flow_nsh *key)
     version = nsh_get_ver(nsh);
     flags = nsh_get_flags(nsh);
     length = nsh_hdr_len(nsh);
+    ttl = nsh_get_ttl(nsh);
 
     if (OVS_UNLIKELY(length > *sizep || version != 0)) {
         return false;
     }
 
     key->flags = flags;
+    key->ttl = ttl;
     key->mdtype = nsh->md_type;
     key->np = nsh->next_proto;
-
-    path_hdr = ntohl(get_16aligned_be32(&nsh->path_hdr));
-    key->si = (path_hdr & NSH_SI_MASK) >> NSH_SI_SHIFT;
-    key->spi = htonl((path_hdr & NSH_SPI_MASK) >> NSH_SPI_SHIFT);
+    key->path_hdr = nsh_get_path_hdr(nsh);
 
     switch (key->mdtype) {
         case NSH_M_TYPE1:
@@ -876,11 +874,11 @@  miniflow_extract(struct dp_packet *packet, struct miniflow *dst)
                 miniflow_pad_to_64(mf, arp_tha);
             }
         } else if (dl_type == htons(ETH_TYPE_NSH)) {
-            struct flow_nsh nsh;
+            struct ovs_key_nsh nsh;
 
             if (OVS_LIKELY(parse_nsh(&data, &size, &nsh))) {
                 miniflow_push_words(mf, nsh, &nsh,
-                                    sizeof(struct flow_nsh) /
+                                    sizeof(struct ovs_key_nsh) /
                                     sizeof(uint64_t));
             }
         }
@@ -1684,10 +1682,10 @@  flow_wildcards_init_for_packet(struct flow_wildcards *wc,
         return;
     } else if (flow->dl_type == htons(ETH_TYPE_NSH)) {
         WC_MASK_FIELD(wc, nsh.flags);
+        WC_MASK_FIELD(wc, nsh.ttl);
         WC_MASK_FIELD(wc, nsh.mdtype);
         WC_MASK_FIELD(wc, nsh.np);
-        WC_MASK_FIELD(wc, nsh.spi);
-        WC_MASK_FIELD(wc, nsh.si);
+        WC_MASK_FIELD(wc, nsh.path_hdr);
         WC_MASK_FIELD(wc, nsh.context);
     } else {
         return; /* Unknown ethertype. */
@@ -1820,8 +1818,7 @@  flow_wc_map(const struct flow *flow, struct flowmap *map)
         FLOWMAP_SET(map, nsh.flags);
         FLOWMAP_SET(map, nsh.mdtype);
         FLOWMAP_SET(map, nsh.np);
-        FLOWMAP_SET(map, nsh.spi);
-        FLOWMAP_SET(map, nsh.si);
+        FLOWMAP_SET(map, nsh.path_hdr);
         FLOWMAP_SET(map, nsh.context);
     }
 }
diff --git a/lib/flow.h b/lib/flow.h
index b3128da..eb1e2bf 100644
--- a/lib/flow.h
+++ b/lib/flow.h
@@ -129,7 +129,7 @@  bool flow_compose(struct dp_packet *, const struct flow *, size_t);
 bool parse_ipv6_ext_hdrs(const void **datap, size_t *sizep, uint8_t *nw_proto,
                          uint8_t *nw_frag);
 ovs_be16 parse_dl_type(const struct eth_header *data_, size_t size);
-bool parse_nsh(const void **datap, size_t *sizep, struct flow_nsh *key);
+bool parse_nsh(const void **datap, size_t *sizep, struct ovs_key_nsh *key);
 
 static inline uint64_t
 flow_get_xreg(const struct flow *flow, int idx)
diff --git a/lib/match.c b/lib/match.c
index 8952c99..b880492 100644
--- a/lib/match.c
+++ b/lib/match.c
@@ -1260,11 +1260,19 @@  format_ct_label_masked(struct ds *s, const ovs_u128 *key, const ovs_u128 *mask)
 static void
 format_nsh_masked(struct ds *s, const struct flow *f, const struct flow *m)
 {
+    ovs_be32 spi_mask = nsh_path_hdr_to_spi(m->nsh.path_hdr);
+    if (spi_mask == htonl(NSH_SPI_MASK >> NSH_SPI_SHIFT)) {
+        spi_mask = OVS_BE32_MAX;
+    }
     format_uint8_masked(s, "nsh_flags", f->nsh.flags, m->nsh.flags);
+    format_uint8_masked(s, "nsh_ttl", f->nsh.ttl, m->nsh.ttl);
     format_uint8_masked(s, "nsh_mdtype", f->nsh.mdtype, m->nsh.mdtype);
     format_uint8_masked(s, "nsh_np", f->nsh.np, m->nsh.np);
-    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);
+
+    format_be32_masked_hex(s, "nsh_spi", nsh_path_hdr_to_spi(f->nsh.path_hdr),
+                           spi_mask);
+    format_uint8_masked(s, "nsh_si", nsh_path_hdr_to_si(f->nsh.path_hdr),
+                        nsh_path_hdr_to_si(m->nsh.path_hdr));
     if (m->nsh.mdtype == UINT8_MAX && f->nsh.mdtype == NSH_M_TYPE1) {
         format_be32_masked_hex(s, "nsh_c1", f->nsh.context[0],
                                m->nsh.context[0]);
diff --git a/lib/meta-flow.c b/lib/meta-flow.c
index beeddf1..ed41f50 100644
--- a/lib/meta-flow.c
+++ b/lib/meta-flow.c
@@ -40,6 +40,7 @@ 
 #include "openvswitch/ofp-errors.h"
 #include "openvswitch/vlog.h"
 #include "vl-mff-map.h"
+#include "openvswitch/nsh.h"
 
 VLOG_DEFINE_THIS_MODULE(meta_flow);
 
@@ -361,14 +362,16 @@  mf_is_all_wild(const struct mf_field *mf, const struct flow_wildcards *wc)
 
     case MFF_NSH_FLAGS:
         return !wc->masks.nsh.flags;
+    case MFF_NSH_TTL:
+        return !wc->masks.nsh.ttl;
     case MFF_NSH_MDTYPE:
         return !wc->masks.nsh.mdtype;
     case MFF_NSH_NP:
         return !wc->masks.nsh.np;
     case MFF_NSH_SPI:
-        return !wc->masks.nsh.spi;
+        return !(wc->masks.nsh.path_hdr & htonl(NSH_SPI_MASK));
     case MFF_NSH_SI:
-        return !wc->masks.nsh.si;
+        return !(wc->masks.nsh.path_hdr & htonl(NSH_SI_MASK));
     case MFF_NSH_C1:
     case MFF_NSH_C2:
     case MFF_NSH_C3:
@@ -606,6 +609,8 @@  mf_is_value_valid(const struct mf_field *mf, const union mf_value *value)
 
     case MFF_NSH_FLAGS:
         return true;
+    case MFF_NSH_TTL:
+        return (value->u8 <= 63);
     case MFF_NSH_MDTYPE:
         return (value->u8 == 1 || value->u8 == 2);
     case MFF_NSH_NP:
@@ -899,6 +904,9 @@  mf_get_value(const struct mf_field *mf, const struct flow *flow,
     case MFF_NSH_FLAGS:
         value->u8 = flow->nsh.flags;
         break;
+    case MFF_NSH_TTL:
+        value->u8 = flow->nsh.ttl;
+        break;
     case MFF_NSH_MDTYPE:
         value->u8 = flow->nsh.mdtype;
         break;
@@ -906,10 +914,13 @@  mf_get_value(const struct mf_field *mf, const struct flow *flow,
         value->u8 = flow->nsh.np;
         break;
     case MFF_NSH_SPI:
-        value->be32 = flow->nsh.spi;
+        value->be32 = nsh_path_hdr_to_spi(flow->nsh.path_hdr);
+        if (value->be32 == htonl(NSH_SPI_MASK >> NSH_SPI_SHIFT)) {
+            value->be32 = OVS_BE32_MAX;
+        }
         break;
     case MFF_NSH_SI:
-        value->u8 = flow->nsh.si;
+        value->u8 = nsh_path_hdr_to_si(flow->nsh.path_hdr);
         break;
     case MFF_NSH_C1:
     case MFF_NSH_C2:
@@ -1214,6 +1225,9 @@  mf_set_value(const struct mf_field *mf,
     case MFF_NSH_FLAGS:
         MATCH_SET_FIELD_UINT8(match, nsh.flags, value->u8);
         break;
+    case MFF_NSH_TTL:
+        MATCH_SET_FIELD_UINT8(match, nsh.ttl, value->u8);
+        break;
     case MFF_NSH_MDTYPE:
         MATCH_SET_FIELD_UINT8(match, nsh.mdtype, value->u8);
         break;
@@ -1221,10 +1235,12 @@  mf_set_value(const struct mf_field *mf,
         MATCH_SET_FIELD_UINT8(match, nsh.np, value->u8);
         break;
     case MFF_NSH_SPI:
-        MATCH_SET_FIELD_BE32(match, nsh.spi, value->be32);
+        match->wc.masks.nsh.path_hdr |= htonl(NSH_SPI_MASK);
+        nsh_path_hdr_set_spi(&match->flow.nsh.path_hdr, value->be32);
         break;
     case MFF_NSH_SI:
-        MATCH_SET_FIELD_UINT8(match, nsh.si, value->u8);
+        match->wc.masks.nsh.path_hdr |= htonl(NSH_SI_MASK);
+        nsh_path_hdr_set_si(&match->flow.nsh.path_hdr, value->u8);
         break;
     case MFF_NSH_C1:
     case MFF_NSH_C2:
@@ -1606,6 +1622,9 @@  mf_set_flow_value(const struct mf_field *mf,
     case MFF_NSH_FLAGS:
         flow->nsh.flags = value->u8;
         break;
+    case MFF_NSH_TTL:
+        flow->nsh.ttl = value->u8;
+        break;
     case MFF_NSH_MDTYPE:
         flow->nsh.mdtype = value->u8;
         break;
@@ -1613,10 +1632,10 @@  mf_set_flow_value(const struct mf_field *mf,
         flow->nsh.np = value->u8;
         break;
     case MFF_NSH_SPI:
-        flow->nsh.spi = value->be32;
+        nsh_path_hdr_set_spi(&flow->nsh.path_hdr, value->be32);
         break;
     case MFF_NSH_SI:
-        flow->nsh.si = value->u8;
+        nsh_path_hdr_set_si(&flow->nsh.path_hdr, value->u8);
         break;
     case MFF_NSH_C1:
     case MFF_NSH_C2:
@@ -1752,6 +1771,7 @@  mf_is_pipeline_field(const struct mf_field *mf)
     case MFF_ND_SLL:
     case MFF_ND_TLL:
     case MFF_NSH_FLAGS:
+    case MFF_NSH_TTL:
     case MFF_NSH_MDTYPE:
     case MFF_NSH_NP:
     case MFF_NSH_SPI:
@@ -2097,6 +2117,9 @@  mf_set_wild(const struct mf_field *mf, struct match *match, char **err_str)
     case MFF_NSH_FLAGS:
         MATCH_SET_FIELD_MASKED(match, nsh.flags, 0, 0);
         break;
+    case MFF_NSH_TTL:
+        MATCH_SET_FIELD_MASKED(match, nsh.ttl, 0, 0);
+        break;
     case MFF_NSH_MDTYPE:
         MATCH_SET_FIELD_MASKED(match, nsh.mdtype, 0, 0);
         break;
@@ -2104,10 +2127,12 @@  mf_set_wild(const struct mf_field *mf, struct match *match, char **err_str)
         MATCH_SET_FIELD_MASKED(match, nsh.np, 0, 0);
         break;
     case MFF_NSH_SPI:
-        MATCH_SET_FIELD_MASKED(match, nsh.spi, htonl(0), htonl(0));
+        match->wc.masks.nsh.path_hdr &= ~htonl(NSH_SPI_MASK);
+        nsh_path_hdr_set_spi(&match->flow.nsh.path_hdr, htonl(0));
         break;
     case MFF_NSH_SI:
-        MATCH_SET_FIELD_MASKED(match, nsh.si, 0, 0);
+        match->wc.masks.nsh.path_hdr &= ~htonl(NSH_SI_MASK);
+        nsh_path_hdr_set_si(&match->flow.nsh.path_hdr, 0);
         break;
     case MFF_NSH_C1:
     case MFF_NSH_C2:
@@ -2357,6 +2382,9 @@  mf_set(const struct mf_field *mf,
     case MFF_NSH_FLAGS:
         MATCH_SET_FIELD_MASKED(match, nsh.flags, value->u8, mask->u8);
         break;
+    case MFF_NSH_TTL:
+        MATCH_SET_FIELD_MASKED(match, nsh.ttl, value->u8, mask->u8);
+        break;
     case MFF_NSH_MDTYPE:
         MATCH_SET_FIELD_MASKED(match, nsh.mdtype, value->u8, mask->u8);
         break;
@@ -2364,10 +2392,14 @@  mf_set(const struct mf_field *mf,
         MATCH_SET_FIELD_MASKED(match, nsh.np, value->u8, mask->u8);
         break;
     case MFF_NSH_SPI:
-        MATCH_SET_FIELD_MASKED(match, nsh.spi, value->be32, mask->be32);
+        match->wc.masks.nsh.path_hdr |= mask->be32;
+        nsh_path_hdr_set_spi(&match->flow.nsh.path_hdr,
+                             value->be32 & mask->be32);
         break;
     case MFF_NSH_SI:
-        MATCH_SET_FIELD_MASKED(match, nsh.si, value->u8, mask->u8);
+        match->wc.masks.nsh.path_hdr |= htonl(mask->u8);
+        nsh_path_hdr_set_si(&match->flow.nsh.path_hdr,
+                             value->u8 & mask->u8);
         break;
     case MFF_NSH_C1:
     case MFF_NSH_C2:
diff --git a/lib/meta-flow.xml b/lib/meta-flow.xml
index 08ee0ec..8e00c68 100644
--- a/lib/meta-flow.xml
+++ b/lib/meta-flow.xml
@@ -1311,7 +1311,9 @@  tcp,tp_src=0x07c0/0xfff0
 
   <group title="Network Service Header">
     <field id="MFF_NSH_FLAGS"
-        title="flags field (8 bits)"/>
+        title="flags field (2 bits)"/>
+    <field id="MFF_NSH_TTL"
+        title="TTL field (6 bits)"/>
     <field id="MFF_NSH_MDTYPE"
         title="mdtype field (8 bits)"/>
     <field id="MFF_NSH_NP"
@@ -3964,7 +3966,7 @@  r r c c c.
       listen for <code>OFPR_INVALID_TTL</code> ``packet-in'' messages via
       OpenFlow.
     </field>
-    
+
     <field id="MFF_IP_FRAG" title="IPv4/v6 Fragment Bitmask">
       <p>
         Specifies what kinds of IP fragments or non-fragments to match.  The
diff --git a/lib/nx-match.c b/lib/nx-match.c
index 8f2a442..aa7691a 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -1022,6 +1022,7 @@  nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, const struct match *match,
     const struct flow *flow = &match->flow;
     const size_t start_len = b->size;
     ovs_be16 dl_type = get_dl_type(flow);
+    ovs_be32 spi_mask;
     int match_len;
     int i;
 
@@ -1157,13 +1158,22 @@  nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, const struct match *match,
     /* Network Service Header */
     nxm_put_8m(&ctx, MFF_NSH_FLAGS, oxm, flow->nsh.flags,
             match->wc.masks.nsh.flags);
+    nxm_put_8m(&ctx, MFF_NSH_TTL, oxm, flow->nsh.ttl,
+            match->wc.masks.nsh.ttl);
     nxm_put_8m(&ctx, MFF_NSH_MDTYPE, oxm, flow->nsh.mdtype,
             match->wc.masks.nsh.mdtype);
     nxm_put_8m(&ctx, MFF_NSH_NP, oxm, flow->nsh.np,
             match->wc.masks.nsh.np);
-    nxm_put_32m(&ctx, MFF_NSH_SPI, oxm, flow->nsh.spi,
-                match->wc.masks.nsh.spi);
-    nxm_put_8m(&ctx, MFF_NSH_SI, oxm, flow->nsh.si, match->wc.masks.nsh.si);
+    spi_mask = nsh_path_hdr_to_spi(match->wc.masks.nsh.path_hdr);
+    if (spi_mask == htonl(NSH_SPI_MASK >> NSH_SPI_SHIFT)) {
+        spi_mask = OVS_BE32_MAX;
+    }
+    nxm_put_32m(&ctx, MFF_NSH_SPI, oxm,
+                nsh_path_hdr_to_spi(flow->nsh.path_hdr),
+                spi_mask);
+    nxm_put_8m(&ctx, MFF_NSH_SI, oxm,
+                nsh_path_hdr_to_si(flow->nsh.path_hdr),
+                nsh_path_hdr_to_si(match->wc.masks.nsh.path_hdr));
     for (int i = 0; i < 4; i++) {
         nxm_put_32m(&ctx, MFF_NSH_C1 + i, oxm, flow->nsh.context[i],
                     match->wc.masks.nsh.context[i]);
diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index 7f9d419..c680364 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -274,19 +274,16 @@  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 flow_nsh *key,
-            const struct flow_nsh *mask)
+odp_set_nsh(struct dp_packet *packet, const struct ovs_key_nsh *key,
+            const struct ovs_key_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));
-        path_hdr = htonl((ntohl(key->spi) << NSH_SPI_SHIFT) |
-                         key->si);
-        put_16aligned_be32(&nsh->path_hdr, path_hdr);
+        nsh_set_flags_and_ttl(nsh, key->flags, key->ttl);
+        put_16aligned_be32(&nsh->path_hdr, key->path_hdr);
         switch (mdtype) {
             case NSH_M_TYPE1:
                 for (int i = 0; i < 4; i++) {
@@ -299,22 +296,25 @@  odp_set_nsh(struct dp_packet *packet, const struct flow_nsh *key,
                 break;
         }
     } else {
-        uint8_t flags = (ntohs(nsh->ver_flags_ttl_len) & NSH_FLAGS_MASK) >>
-                            NSH_FLAGS_SHIFT;
+        uint8_t flags = nsh_get_flags(nsh);
+        uint8_t ttl = nsh_get_ttl(nsh);
+
         flags = key->flags | (flags & ~mask->flags);
-        nsh->ver_flags_ttl_len = htons(flags << NSH_FLAGS_SHIFT) |
-                (nsh->ver_flags_ttl_len & ~htons(NSH_FLAGS_MASK));
+        ttl = key->ttl | (ttl & ~mask->ttl);
+        nsh_set_flags_and_ttl(nsh, flags, ttl);
 
-        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);
+        uint32_t spi = ntohl(nsh_get_spi(nsh));
+        uint8_t si = nsh_get_si(nsh);
+        uint32_t spi_mask = nsh_path_hdr_to_spi_uint32(mask->path_hdr);
+        uint8_t si_mask = nsh_path_hdr_to_si(mask->path_hdr);
         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);
+        spi = nsh_path_hdr_to_spi_uint32(key->path_hdr) | (spi & ~spi_mask);
+        si = nsh_path_hdr_to_si(key->path_hdr) | (si & ~si_mask);
+        path_hdr = nsh_get_path_hdr(nsh);
+        nsh_path_hdr_set_spi(&path_hdr, htonl(spi));
+        nsh_path_hdr_set_si(&path_hdr, si);
         put_16aligned_be32(&nsh->path_hdr, path_hdr);
         switch (mdtype) {
             case NSH_M_TYPE1:
@@ -359,7 +359,7 @@  odp_execute_set_action(struct dp_packet *packet, const struct nlattr *a)
         break;
 
     case OVS_KEY_ATTR_NSH: {
-        struct flow_nsh nsh;
+        struct ovs_key_nsh nsh;
         odp_nsh_key_from_attr(a, &nsh);
         odp_set_nsh(packet, &nsh, NULL);
         break;
@@ -490,7 +490,7 @@  odp_execute_masked_set_action(struct dp_packet *packet,
         break;
 
     case OVS_KEY_ATTR_NSH: {
-        struct flow_nsh nsh, nsh_mask;
+        struct ovs_key_nsh nsh, nsh_mask;
         struct {
             struct nlattr nla;
             uint8_t data[sizeof(struct ovs_nsh_key_base) + NSH_CTX_HDRS_MAX_LEN
diff --git a/lib/odp-util.c b/lib/odp-util.c
index ff08821..95fef7e 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -246,12 +246,13 @@  static void
 format_nsh_key(struct ds *ds, const struct ovs_key_nsh *key)
 {
     ds_put_format(ds, "flags=%d", key->flags);
+    ds_put_format(ds, "ttl=%d", key->ttl);
     ds_put_format(ds, ",mdtype=%d", key->mdtype);
     ds_put_format(ds, ",np=%d", key->np);
     ds_put_format(ds, ",spi=0x%x",
-                  (ntohl(key->path_hdr) & NSH_SPI_MASK) >> NSH_SPI_SHIFT);
+                  nsh_path_hdr_to_spi_uint32(key->path_hdr));
     ds_put_format(ds, ",si=%d",
-                  (ntohl(key->path_hdr) & NSH_SI_MASK) >> NSH_SI_SHIFT);
+                  nsh_path_hdr_to_si(key->path_hdr));
 
     switch (key->mdtype) {
         case NSH_M_TYPE1:
@@ -311,17 +312,16 @@  format_nsh_key_mask(struct ds *ds, const struct ovs_key_nsh *key,
         format_nsh_key(ds, key);
     } else {
         bool first = true;
-        uint32_t spi = (ntohl(key->path_hdr) & NSH_SPI_MASK) >> NSH_SPI_SHIFT;
-        uint32_t spi_mask = (ntohl(mask->path_hdr) & NSH_SPI_MASK) >>
-                                NSH_SPI_SHIFT;
-        if (spi_mask == 0x00ffffff) {
+        uint32_t spi = nsh_path_hdr_to_spi_uint32(key->path_hdr);
+        uint32_t spi_mask = nsh_path_hdr_to_spi_uint32(mask->path_hdr);
+        if (spi_mask == (NSH_SPI_MASK >> NSH_SPI_SHIFT)) {
             spi_mask = UINT32_MAX;
         }
-        uint8_t si = (ntohl(key->path_hdr) & NSH_SI_MASK) >> NSH_SI_SHIFT;
-        uint8_t si_mask = (ntohl(mask->path_hdr) & NSH_SI_MASK) >>
-                              NSH_SI_SHIFT;
+        uint8_t si = nsh_path_hdr_to_si(key->path_hdr);
+        uint8_t si_mask = nsh_path_hdr_to_si(mask->path_hdr);
 
         format_uint8_masked(ds, &first, "flags", key->flags, mask->flags);
+        format_uint8_masked(ds, &first, "ttl", key->ttl, mask->ttl);
         format_uint8_masked(ds, &first, "mdtype", key->mdtype, mask->mdtype);
         format_uint8_masked(ds, &first, "np", key->np, mask->np);
         format_be32_masked(ds, &first, "spi", htonl(spi), htonl(spi_mask));
@@ -342,13 +342,14 @@  format_odp_push_nsh_action(struct ds *ds,
                            const struct nsh_hdr *nsh_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;
+    uint32_t spi = ntohl(nsh_get_spi(nsh_hdr));
+    uint8_t si = nsh_get_si(nsh_hdr);
     uint8_t flags = nsh_get_flags(nsh_hdr);
+    uint8_t ttl = nsh_get_ttl(nsh_hdr);
 
     ds_put_cstr(ds, "push_nsh(");
     ds_put_format(ds, "flags=%d", flags);
+    ds_put_format(ds, ",ttl=%d", ttl);
     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);
@@ -1785,18 +1786,14 @@  find_end:
 }
 
 static void
-nsh_key_to_attr(struct ofpbuf *buf, const struct flow_nsh *nsh,
+nsh_key_to_attr(struct ofpbuf *buf, const struct ovs_key_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);
+    memcpy(&base, nsh, sizeof(base));
 
     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);
@@ -1831,8 +1828,9 @@  parse_odp_push_nsh_action(const char *s, struct ofpbuf *actions)
     int n = 0;
     int ret = 0;
     uint32_t spi = 0;
+    uint8_t si = 255;
     uint32_t cd;
-    struct flow_nsh nsh;
+    struct ovs_key_nsh nsh;
     uint8_t metadata[NSH_CTX_HDRS_MAX_LEN];
     uint8_t md_size = 0;
 
@@ -1843,10 +1841,10 @@  parse_odp_push_nsh_action(const char *s, struct ofpbuf *actions)
 
     /* The default is NSH_M_TYPE1 */
     nsh.flags = 0;
+    nsh.ttl = 63;
     nsh.mdtype = NSH_M_TYPE1;
     nsh.np = NSH_P_ETHERNET;
-    nsh.spi = 0;
-    nsh.si = 255;
+    nsh.path_hdr = nsh_spi_si_to_path_hdr(0, 255);
     memset(nsh.context, 0, NSH_M_TYPE1_MDLEN);
 
     for (;;) {
@@ -1858,6 +1856,9 @@  parse_odp_push_nsh_action(const char *s, struct ofpbuf *actions)
         if (ovs_scan_len(s, &n, "flags=%"SCNi8, &nsh.flags)) {
             continue;
         }
+        if (ovs_scan_len(s, &n, "ttl=%"SCNi8, &nsh.ttl)) {
+            continue;
+        }
         if (ovs_scan_len(s, &n, "mdtype=%"SCNi8, &nsh.mdtype)) {
             switch (nsh.mdtype) {
             case NSH_M_TYPE1:
@@ -1877,10 +1878,9 @@  parse_odp_push_nsh_action(const char *s, struct ofpbuf *actions)
             continue;
         }
         if (ovs_scan_len(s, &n, "spi=0x%"SCNx32, &spi)) {
-            nsh.spi = htonl(spi);
             continue;
         }
-        if (ovs_scan_len(s, &n, "si=%"SCNi8, &nsh.si)) {
+        if (ovs_scan_len(s, &n, "si=%"SCNi8, &si)) {
             continue;
         }
         if (nsh.mdtype == NSH_M_TYPE1) {
@@ -1925,6 +1925,7 @@  parse_odp_push_nsh_action(const char *s, struct ofpbuf *actions)
     }
 out:
     if (ret >= 0) {
+        nsh.path_hdr = nsh_spi_si_to_path_hdr(spi, si);
         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);
@@ -2352,6 +2353,7 @@  odp_nsh_hdr_from_attr(const struct nlattr *attr,
     const struct nlattr *a;
     bool unknown = false;
     uint8_t flags = 0;
+    uint8_t ttl = 63;
     size_t mdlen = 0;
     bool has_md1 = false;
     bool has_md2 = false;
@@ -2373,6 +2375,7 @@  odp_nsh_hdr_from_attr(const struct nlattr *attr,
             nsh_hdr->md_type = base->mdtype;
             put_16aligned_be32(&nsh_hdr->path_hdr, base->path_hdr);
             flags = base->flags;
+            ttl = base->ttl;
             break;
         }
         case OVS_NSH_KEY_ATTR_MD1: {
@@ -2416,14 +2419,13 @@  odp_nsh_hdr_from_attr(const struct nlattr *attr,
     }
 
     /* 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);
+    nsh_set_flags_ttl_len(nsh_hdr, flags, ttl, NSH_BASE_HDR_LEN + mdlen);
 
     return ODP_FIT_PERFECT;
 }
 
 enum odp_key_fitness
-odp_nsh_key_from_attr(const struct nlattr *attr, struct flow_nsh *nsh)
+odp_nsh_key_from_attr(const struct nlattr *attr, struct ovs_key_nsh *nsh)
 {
     unsigned int left;
     const struct nlattr *a;
@@ -2443,12 +2445,7 @@  odp_nsh_key_from_attr(const struct nlattr *attr, struct flow_nsh *nsh)
         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;
+            memcpy(nsh, base, sizeof(*base));
             break;
         }
         case OVS_NSH_KEY_ATTR_MD1: {
@@ -3197,24 +3194,18 @@  format_odp_nsh_attr(const struct nlattr *attr, const struct nlattr *mask_attr,
         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
+            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;
+            memcpy(&nsh, base, sizeof(*base));
             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;
+                memcpy(&nsh_mask, base_mask, sizeof(*base_mask));
             }
             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
+            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) {
@@ -4822,7 +4813,9 @@  parse_odp_nsh_key_mask_attr(const char *s, struct ofpbuf *key,
     if (strncmp(s, "nsh(", 4) == 0) {
         const char *start = s;
         int len;
-        struct flow_nsh skey, smask;
+        struct ovs_key_nsh skey, smask;
+        uint32_t spi = 0, spi_mask = 0;
+        uint8_t si = 0, si_mask = 0;
 
         s += 4;
 
@@ -4863,7 +4856,7 @@  parse_odp_nsh_key_mask_attr(const char *s, struct ofpbuf *key,
 
             if (strncmp(s, "spi=", 4) == 0) {
                 s += 4;
-                len = scan_be32(s, &skey.spi, mask ? &smask.spi : NULL);
+                len = scan_be32(s, &spi, mask ? &spi_mask : NULL);
                 if (len == 0) {
                     return -EINVAL;
                 }
@@ -4873,7 +4866,7 @@  parse_odp_nsh_key_mask_attr(const char *s, struct ofpbuf *key,
 
             if (strncmp(s, "si=", 3) == 0) {
                 s += 3;
-                len = scan_u8(s, &skey.si, mask ? &smask.si : NULL);
+                len = scan_u8(s, &si, mask ? &si_mask : NULL);
                 if (len == 0) {
                     return -EINVAL;
                 }
@@ -4929,6 +4922,9 @@  parse_odp_nsh_key_mask_attr(const char *s, struct ofpbuf *key,
             return -EINVAL;
         }
 
+        skey.path_hdr = nsh_spi_si_to_path_hdr(spi, si);
+        smask.path_hdr = nsh_spi_si_to_path_hdr(spi_mask, si_mask);
+
         nsh_key_to_attr(key, &skey, NULL, 0, false);
         if (mask) {
             nsh_key_to_attr(mask, &smask, NULL, 0, true);
@@ -6990,59 +6986,29 @@  commit_set_nw_action(const struct flow *flow, struct flow *base,
     return 0;
 }
 
-static void
+static inline void
 get_nsh_key(const struct flow *flow, struct ovs_key_nsh *nsh, bool is_mask)
 {
-    nsh->flags = flow->nsh.flags;
-    nsh->mdtype = flow->nsh.mdtype;
-    nsh->np = flow->nsh.np;
-    nsh->path_hdr = htonl((ntohl(flow->nsh.spi) << NSH_SPI_SHIFT) |
-                          flow->nsh.si);
-    if (is_mask) {
-        for (int i = 0; i < 4; i++) {
-            nsh->context[i] = flow->nsh.context[i];
-        }
-    } else {
-        switch (nsh->mdtype) {
-        case NSH_M_TYPE1:
-            for (int i = 0; i < 4; i++) {
-                nsh->context[i] = flow->nsh.context[i];
-            }
-            break;
-        case NSH_M_TYPE2:
-        default:
-            /* No match support for other MD formats yet. */
-            break;
+    memcpy(nsh, &flow->nsh, sizeof(*nsh));
+    if (!is_mask) {
+        if (nsh->mdtype != NSH_M_TYPE1) {
+            memset(nsh, 0, sizeof(nsh->context));
         }
     }
 }
 
-static void
+static inline void
 put_nsh_key(const struct ovs_key_nsh *nsh, struct flow *flow,
             bool is_mask OVS_UNUSED)
 {
-    flow->nsh.flags = nsh->flags;
-    flow->nsh.mdtype = nsh->mdtype;
-    flow->nsh.np = nsh->np;
-    flow->nsh.spi = htonl((ntohl(nsh->path_hdr) & NSH_SPI_MASK) >>
-                              NSH_SPI_SHIFT);
-    flow->nsh.si = (ntohl(nsh->path_hdr) & NSH_SI_MASK) >> NSH_SI_SHIFT;
-    switch (nsh->mdtype) {
-        case NSH_M_TYPE1:
-            for (int i = 0; i < 4; 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.context, 0, sizeof flow->nsh.context);
-            break;
+    memcpy(&flow->nsh, nsh, sizeof(*nsh));
+    if (flow->nsh.mdtype != NSH_M_TYPE1) {
+        memset(flow->nsh.context, 0, sizeof(flow->nsh.context));
     }
 }
 
 static bool
-commit_nsh(const struct flow_nsh * flow_nsh, bool use_masked_set,
+commit_nsh(const struct ovs_key_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)
@@ -7067,15 +7033,8 @@  commit_nsh(const struct flow_nsh * flow_nsh, bool use_masked_set,
         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;
+        memcpy(&nsh_base, key, sizeof(nsh_base));
+        memcpy(&nsh_base_mask, mask, sizeof(nsh_base_mask));
 
         /* OVS_KEY_ATTR_NSH keys */
         nsh_key_ofs = nl_msg_start_nested(odp_actions, OVS_KEY_ATTR_NSH);
diff --git a/lib/odp-util.h b/lib/odp-util.h
index f7ce206..fafea62 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -159,7 +159,7 @@  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 *);
+                                           struct ovs_key_nsh *);
 enum odp_key_fitness odp_nsh_hdr_from_attr(const struct nlattr *,
                                            struct nsh_hdr *, size_t size);
 
diff --git a/lib/packets.c b/lib/packets.c
index 8ebae8c..4a3bee6 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -441,6 +441,7 @@  push_nsh(struct dp_packet *packet, const struct nsh_hdr *nsh_hdr_src)
     nsh = (struct nsh_hdr *) dp_packet_push_uninit(packet, length);
     memcpy(nsh, nsh_hdr_src, length);
     nsh->next_proto = next_proto;
+    nsh->md_type &= NSH_MDTYPE_MASK;
     packet->packet_type = htonl(PT_NSH);
     dp_packet_reset_offsets(packet);
     packet->l3_ofs = 0;
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 4506150..bf8b060 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -5926,10 +5926,10 @@  rewrite_flow_push_nsh(struct xlate_ctx *ctx,
     /* Populate the flow with the new NSH header. */
     flow->packet_type = htonl(PT_NSH);
     flow->dl_type = htons(ETH_TYPE_NSH);
-    flow->nsh.flags = 0;    /* */
+    flow->nsh.flags = 0;
+    flow->nsh.ttl = 63;
     flow->nsh.np = np;
-    flow->nsh.spi = 0;
-    flow->nsh.si = 255;
+    flow->nsh.path_hdr = htonl(255);
 
     if (md_type == NSH_M_TYPE1) {
         flow->nsh.mdtype = NSH_M_TYPE1;
@@ -5942,6 +5942,7 @@  rewrite_flow_push_nsh(struct xlate_ctx *ctx,
     } else if (md_type == NSH_M_TYPE2) {
         flow->nsh.mdtype = NSH_M_TYPE2;
     }
+    flow->nsh.mdtype &= NSH_MDTYPE_MASK;
 
     return buf;
 }
diff --git a/tests/nsh.at b/tests/nsh.at
index bec6e87..6177cea 100644
--- a/tests/nsh.at
+++ b/tests/nsh.at
@@ -13,7 +13,7 @@  OVS_VSWITCHD_START([dnl
     add-port br0 p2 -- set Interface p2 type=dummy ofport_request=2])
 
 AT_DATA([flows.txt], [dnl
-    table=0,in_port=1,dl_type=0x894f,nsh_mdtype=1,nsh_np=3,nsh_spi=0x123456,nsh_si=255,nsh_c1=0x11223344,actions=set_field:0x80->nsh_flags,set_field:254->nsh_si,set_field:0x44332211->nsh_c1,2
+    table=0,in_port=1,dl_type=0x894f,nsh_ttl=63,nsh_mdtype=1,nsh_np=3,nsh_spi=0x123456,nsh_si=255,nsh_c1=0x11223344,actions=set_field:0x2->nsh_flags,set_field:254->nsh_si,set_field:0x44332211->nsh_c1,2
 ])
 
 AT_CHECK([
@@ -21,25 +21,25 @@  AT_CHECK([
     ovs-ofctl -Oopenflow13 add-flows br0 flows.txt
     ovs-ofctl -Oopenflow13 dump-flows br0 | ofctl_strip | sort | grep actions
 ], [0], [dnl
- in_port=1,dl_type=0x894f,nsh_mdtype=1,nsh_np=3,nsh_spi=0x123456,nsh_si=255,nsh_c1=0x11223344 actions=set_field:128->nsh_flags,set_field:254->nsh_si,set_field:0x44332211->nsh_c1,output:2
+ in_port=1,dl_type=0x894f,nsh_ttl=63,nsh_mdtype=1,nsh_np=3,nsh_spi=0x123456,nsh_si=255,nsh_c1=0x11223344 actions=set_field:2->nsh_flags,set_field:254->nsh_si,set_field:0x44332211->nsh_c1,output:2
 ])
 
 AT_CHECK([
-    ovs-appctl ofproto/trace br0 'in_port=1,dl_type=0x894f,nsh_mdtype=1,nsh_np=3,nsh_spi=0x123456,nsh_si=255,nsh_c1=0x11223344,nsh_c2=0x55667788,nsh_c3=0x99aabbcc,nsh_c4=0xddeeff00'
+    ovs-appctl ofproto/trace br0 'in_port=1,dl_type=0x894f,nsh_ttl=63,nsh_mdtype=1,nsh_np=3,nsh_spi=0x123456,nsh_si=255,nsh_c1=0x11223344,nsh_c2=0x55667788,nsh_c3=0x99aabbcc,nsh_c4=0xddeeff00'
 ], [0], [dnl
-Flow: in_port=1,vlan_tci=0x0000,dl_src=00:00:00:00:00:00,dl_dst=00:00:00:00:00:00,dl_type=0x894f,nsh_flags=0,nsh_mdtype=1,nsh_np=3,nsh_spi=0x123456,nsh_si=255,nsh_c1=0x11223344,nsh_c2=0x55667788,nsh_c3=0x99aabbcc,nsh_c4=0xddeeff00,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0
+Flow: in_port=1,vlan_tci=0x0000,dl_src=00:00:00:00:00:00,dl_dst=00:00:00:00:00:00,dl_type=0x894f,nsh_flags=0,nsh_ttl=63,nsh_mdtype=1,nsh_np=3,nsh_spi=0x123456,nsh_si=255,nsh_c1=0x11223344,nsh_c2=0x55667788,nsh_c3=0x99aabbcc,nsh_c4=0xddeeff00,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0
 
 bridge("br0")
 -------------
- 0. in_port=1,dl_type=0x894f,nsh_mdtype=1,nsh_np=3,nsh_spi=0x123456,nsh_si=255,nsh_c1=0x11223344, priority 32768
-    set_field:128->nsh_flags
+ 0. in_port=1,dl_type=0x894f,nsh_ttl=63,nsh_mdtype=1,nsh_np=3,nsh_spi=0x123456,nsh_si=255,nsh_c1=0x11223344, priority 32768
+    set_field:2->nsh_flags
     set_field:254->nsh_si
     set_field:0x44332211->nsh_c1
     output:2
 
-Final flow: in_port=1,vlan_tci=0x0000,dl_src=00:00:00:00:00:00,dl_dst=00:00:00:00:00:00,dl_type=0x894f,nsh_flags=128,nsh_mdtype=1,nsh_np=3,nsh_spi=0x123456,nsh_si=254,nsh_c1=0x44332211,nsh_c2=0x55667788,nsh_c3=0x99aabbcc,nsh_c4=0xddeeff00,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0
-Megaflow: recirc_id=0,eth,in_port=1,dl_type=0x894f,nsh_flags=0,nsh_mdtype=1,nsh_np=3,nsh_spi=0x123456,nsh_si=255,nsh_c1=0x11223344
-Datapath actions: set(nsh(flags=128,spi=0x123456,si=254,c1=0x44332211)),2
+Final flow: in_port=1,vlan_tci=0x0000,dl_src=00:00:00:00:00:00,dl_dst=00:00:00:00:00:00,dl_type=0x894f,nsh_flags=2,nsh_ttl=63,nsh_mdtype=1,nsh_np=3,nsh_spi=0x123456,nsh_si=254,nsh_c1=0x44332211,nsh_c2=0x55667788,nsh_c3=0x99aabbcc,nsh_c4=0xddeeff00,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0
+Megaflow: recirc_id=0,eth,in_port=1,dl_type=0x894f,nsh_flags=0,nsh_ttl=63,nsh_mdtype=1,nsh_np=3,nsh_spi=0x123456,nsh_si=255,nsh_c1=0x11223344
+Datapath actions: set(nsh(flags=2,ttl=63,spi=0x123456,si=254,c1=0x44332211)),2
 ])
 
 OVS_VSWITCHD_STOP
@@ -103,15 +103,15 @@  bridge("br0")
     decap()
     decap()
 
-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
+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_ttl=63,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: 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)
+Datapath actions: push_nsh(flags=0,ttl=63,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([
     ovs-appctl ofproto/trace br0 'in_port=4,dl_type=0x894f,nsh_mdtype=1,nsh_np=3,nsh_spi=0x1234,nsh_c1=0x11223344'
 ], [0], [dnl
-Flow: in_port=4,vlan_tci=0x0000,dl_src=00:00:00:00:00:00,dl_dst=00:00:00:00:00:00,dl_type=0x894f,nsh_flags=0,nsh_mdtype=1,nsh_np=3,nsh_spi=0x1234,nsh_si=0,nsh_c1=0x11223344,nsh_c2=0x0,nsh_c3=0x0,nsh_c4=0x0,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0
+Flow: in_port=4,vlan_tci=0x0000,dl_src=00:00:00:00:00:00,dl_dst=00:00:00:00:00:00,dl_type=0x894f,nsh_flags=0,nsh_ttl=0,nsh_mdtype=1,nsh_np=3,nsh_spi=0x1234,nsh_si=0,nsh_c1=0x11223344,nsh_c2=0x0,nsh_c3=0x0,nsh_c4=0x0,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0
 
 bridge("br0")
 -------------
@@ -139,7 +139,7 @@  ovs-appctl time/warp 1000
 AT_CHECK([
     ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v ipv6 | sort
 ], [0], [flow-dump from non-dpdk interfaces:
-recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(dst=1e:2c:e9:2a:66:9e),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions: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(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,ttl=63,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
 ])
 
@@ -172,7 +172,7 @@  ovs-appctl time/warp 1000
 AT_CHECK([
     ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v ipv6 | sort
 ], [0], [flow-dump from non-dpdk interfaces:
-recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:push_vlan(vid=100,pcp=0),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(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,ttl=63,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
 ])
 
@@ -230,15 +230,15 @@  bridge("br0")
     decap()
     decap()
 
-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
+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_ttl=63,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: 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)
+Datapath actions: push_nsh(flags=0,ttl=63,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([
     ovs-appctl ofproto/trace br0 'in_port=4,dl_type=0x894f,nsh_mdtype=2,nsh_np=3,nsh_spi=0x1234'
 ], [0], [dnl
-Flow: in_port=4,vlan_tci=0x0000,dl_src=00:00:00:00:00:00,dl_dst=00:00:00:00:00:00,dl_type=0x894f,nsh_flags=0,nsh_mdtype=2,nsh_np=3,nsh_spi=0x1234,nsh_si=0,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0
+Flow: in_port=4,vlan_tci=0x0000,dl_src=00:00:00:00:00:00,dl_dst=00:00:00:00:00:00,dl_type=0x894f,nsh_flags=0,nsh_ttl=0,nsh_mdtype=2,nsh_np=3,nsh_spi=0x1234,nsh_si=0,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0
 
 bridge("br0")
 -------------
@@ -266,7 +266,7 @@  ovs-appctl time/warp 1000
 AT_CHECK([
     ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v ipv6 | sort
 ], [0], [flow-dump from non-dpdk interfaces:
-recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(dst=1e:2c:e9:2a:66:9e),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions: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(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,ttl=63,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
 ])
 
@@ -575,11 +575,12 @@  AT_CHECK([
 
 ovs-appctl time/warp 1000
 ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
 
 AT_CHECK([
     ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v ipv6 | sort
 ], [0], [flow-dump from non-dpdk interfaces:
-recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=192.168.10.30,frag=no), packets:1, bytes:98, used:0.0s, actions:pop_eth,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))
+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,ttl=63,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
 ])
@@ -633,7 +634,7 @@  ovs-appctl time/warp 1000
 AT_CHECK([
     ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v ipv6 | sort
 ], [0], [flow-dump from non-dpdk interfaces:
-recirc_id(0),in_port(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))
+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,ttl=63,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: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