=====================================================================
OXM_NSH_FLAGS       nsh_flags       8       Bits 2-9 of 1st NSH word
(0x8004,1)
OXM_NSH_MDTYPE      nsh_mdtype      8       Bits 16-23
(0x8004,2)
OXM_NSH_NEXTPROTO   nsh_np          8       Bits 24-31
(0x8004,3)
OXM_NSH_SPI         nsh_spi         24      Bits 0-23 of 2nd NSH word
(0x8004,4)
OXM_NSH_SI          nsh_si          8       Bits 24-31
(0x8004,5)
OXM_NSH_C1          nsh_c1          32      Maskable, nsh_mdtype==1
(0x8004,6)
OXM_NSH_C2          nsh_c2          32      Maskable, nsh_mdtype==1
(0x8004,7)
OXM_NSH_C3          nsh_c3          32      Maskable, nsh_mdtype==1
(0x8004,8)
OXM_NSH_C4          nsh_c4          32      Maskable, nsh_mdtype==1
(0x8004,9)

Co-authored-by: Johnson Li <johnson.li@intel.com>
Signed-off-by: Yi Yang <yi.y.yang@intel.com>
Signed-off-by: Jan Scheurich <jan.scheurich@ericsson.com>
---
 build-aux/extract-ofp-fields                      |   2 +
 datapath/linux/compat/include/linux/openvswitch.h |  15 ++
 include/openvswitch/automake.mk                   |   3 +-
 include/openvswitch/flow.h                        |   8 +-
 include/openvswitch/match.h                       |  12 ++
 include/openvswitch/meta-flow.h                   | 132 +++++++++++++
 include/openvswitch/nsh.h                         | 130 +++++++++++++
 include/openvswitch/packets.h                     |  19 ++
 lib/flow.c                                        | 101 +++++++++-
 lib/flow.h                                        |   3 +-
 lib/match.c                                       |  72 ++++++-
 lib/meta-flow.c                                   | 181 ++++++++++++++++++
 lib/meta-flow.xml                                 |  21 +++
 lib/nx-match.c                                    |  21 ++-
 lib/odp-execute.c                                 |   4 +
 lib/odp-util.c                                    | 217 ++++++++++++++++++++++
 lib/odp-util.h                                    |   2 +-
 lib/ofp-util.c                                    |   2 +-
 lib/packets.h                                     |   3 +
 ofproto/ofproto-dpif-rid.h                        |   2 +-
 ofproto/ofproto-dpif-sflow.c                      |   1 +
 ofproto/ofproto-dpif-xlate.c                      |   2 +-
 tests/ofproto.at                                  |  11 +-
 23 files changed, 942 insertions(+), 22 deletions(-)
 create mode 100644 include/openvswitch/nsh.h

diff --git a/build-aux/extract-ofp-fields b/build-aux/extract-ofp-fields
index e4bb4dc..7b6151f 100755
--- a/build-aux/extract-ofp-fields
+++ b/build-aux/extract-ofp-fields
@@ -46,6 +46,7 @@ PREREQS = {"none": "MFP_NONE",
            "IPv4": "MFP_IPV4",
            "IPv6": "MFP_IPV6",
            "IPv4/IPv6": "MFP_IP_ANY",
+           "NSH": "MFP_NSH",
            "CT": "MFP_CT_VALID",
            "MPLS": "MFP_MPLS",
            "TCP": "MFP_TCP",
@@ -68,6 +69,7 @@ OXM_CLASSES = {"NXM_OF_":        (0,          0x0000),
                "NXM_NX_":        (0,          0x0001),
                "OXM_OF_":        (0,          0x8000),
                "OXM_OF_PKT_REG": (0,          0x8001),
+               "OXM_NSH_":       (0,          0x8004),
                "ONFOXM_ET_":     (0x4f4e4600, 0xffff),
 
                # This is the experimenter OXM class for Nicira, which is the
diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h
index 9d08dac..2381ed2 100644
--- a/datapath/linux/compat/include/linux/openvswitch.h
+++ b/datapath/linux/compat/include/linux/openvswitch.h
@@ -360,6 +360,9 @@ enum ovs_key_attr {
 	OVS_KEY_ATTR_CT_LABELS,	/* 16-octet connection tracking labels */
 	OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4,   /* struct ovs_key_ct_tuple_ipv4 */
 	OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6,   /* struct ovs_key_ct_tuple_ipv6 */
+#ifndef __KERNEL__
+	OVS_KEY_ATTR_NSH,       /* struct ovs_key_nsh */
+#endif
 
 #ifdef __KERNEL__
 	/* Only used within kernel data path. */
@@ -491,6 +494,18 @@ struct ovs_key_ct_labels {
 	};
 };
 
+struct ovs_key_nsh {
+    __u8 flags;
+    __u8 mdtype;
+    __u8 np;
+    __u8 pad;
+    __be32 path_hdr;
+    __be32 c1;
+    __be32 c2;
+    __be32 c3;
+    __be32 c4;
+};
+
 /* OVS_KEY_ATTR_CT_STATE flags */
 #define OVS_CS_F_NEW               0x01 /* Beginning of a new connection. */
 #define OVS_CS_F_ESTABLISHED       0x02 /* Part of an existing connection. */
diff --git a/include/openvswitch/automake.mk b/include/openvswitch/automake.mk
index 0a139d0..eabeb1e 100644
--- a/include/openvswitch/automake.mk
+++ b/include/openvswitch/automake.mk
@@ -30,7 +30,8 @@ openvswitchinclude_HEADERS = \
 	include/openvswitch/uuid.h \
 	include/openvswitch/version.h \
 	include/openvswitch/vconn.h \
-	include/openvswitch/vlog.h
+	include/openvswitch/vlog.h \
+	include/openvswitch/nsh.h
 
 if HAVE_CXX
 # OVS does not use C++ itself, but it provides public header files
diff --git a/include/openvswitch/flow.h b/include/openvswitch/flow.h
index e9782d7..a658a58 100644
--- a/include/openvswitch/flow.h
+++ b/include/openvswitch/flow.h
@@ -27,7 +27,7 @@ extern "C" {
 /* This sequence number should be incremented whenever anything involving flows
  * or the wildcarding of flows changes.  This will cause build assertion
  * failures in places which likely need to be updated. */
-#define FLOW_WC_SEQ 39
+#define FLOW_WC_SEQ 40
 
 /* Number of Open vSwitch extension 32-bit registers. */
 #define FLOW_N_REGS 16
@@ -146,6 +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 */
 
     /* L4 (64-bit aligned) */
     ovs_be16 tp_src;            /* TCP/UDP/SCTP source port/ICMP type. */
@@ -158,13 +159,14 @@ 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);
 
 #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) + 300
-                  && FLOW_WC_SEQ == 39);
+                  == sizeof(struct flow_tnl) + sizeof(struct flow_nsh) + 300
+                  && FLOW_WC_SEQ == 40);
 
 /* Incremental points at which flow classification may be performed in
  * segments.
diff --git a/include/openvswitch/match.h b/include/openvswitch/match.h
index 1b43de0..a8ed8b3 100644
--- a/include/openvswitch/match.h
+++ b/include/openvswitch/match.h
@@ -46,6 +46,18 @@ struct match {
 /* Initializer for a "struct match" that matches every packet. */
 #define MATCH_CATCHALL_INITIALIZER { .flow = { .dl_type = 0 } }
 
+#define MATCH_SET_FIELD_MASKED(match, field, value, msk)      \
+    do {                                                      \
+        (match)->wc.masks.field = (msk);                      \
+        (match)->flow.field = (value) & (msk);                \
+    } while (0)
+
+#define MATCH_SET_FIELD_UINT8(match, field, value)            \
+    MATCH_SET_FIELD_MASKED(match, field, value, UINT8_MAX)
+
+#define MATCH_SET_FIELD_BE32(match, field, value)             \
+    MATCH_SET_FIELD_MASKED(match, field, value, OVS_BE32_MAX)
+
 void match_init(struct match *,
                 const struct flow *, const struct flow_wildcards *);
 void match_wc_init(struct match *match, const struct flow *flow);
diff --git a/include/openvswitch/meta-flow.h b/include/openvswitch/meta-flow.h
index 7e6d726..6192898 100644
--- a/include/openvswitch/meta-flow.h
+++ b/include/openvswitch/meta-flow.h
@@ -1739,6 +1739,137 @@ enum OVS_PACKED_ENUM mf_field_id {
      */
     MFF_ND_TLL,
 
+/* ## ---- ## */
+/* ## NSH  ## */
+/* ## ---- ## */
+
+    /* "nsh_flags".
+     *
+     * flags field in NSH base header (8 bits).
+     *
+     * Type: u8.
+     * Maskable: bitwise.
+     * Formatting: decimal.
+     * Prerequisites: NSH.
+     * Access: read/write.
+     * NXM: none.
+     * OXM: OXM_NSH_FLAGS(1) since OF1.3 and v2.8.
+     */
+    MFF_NSH_FLAGS,
+
+    /* "nsh_mdtype".
+     *
+     * mdtype field in NSH base header (8 bits).
+     *
+     * Type: u8.
+     * Maskable: no.
+     * Formatting: decimal.
+     * Prerequisites: NSH.
+     * Access: read-only.
+     * NXM: none.
+     * OXM: OXM_NSH_MDTYPE(2) since OF1.3 and v2.8.
+     */
+    MFF_NSH_MDTYPE,
+
+    /* "nsh_np".
+     *
+     * np (next protocol) field in NSH base header (8 bits)
+     *
+     * Type: u8.
+     * Maskable: no.
+     * Formatting: decimal.
+     * Prerequisites: NSH.
+     * Access: read-only.
+     * NXM: none.
+     * OXM: OXM_NSH_NP(3) since OF1.3 and v2.8.
+     */
+    MFF_NSH_NP,
+
+    /* "nsh_spi" (aka "nsp").
+     *
+     * spi (service path identifier) field in NSH base
+     * header (24 bits).
+     *
+     * Type: be32.
+     * Maskable: no.
+     * Formatting: hexadecimal.
+     * Prerequisites: NSH.
+     * Access: read/write.
+     * NXM: none.
+     * OXM: OXM_NSH_SPI(4) since OF1.3 and v2.8.
+     */
+    MFF_NSH_SPI,
+
+    /* "nsh_si" (aka "nsi").
+     *
+     * si (service index) field in NSH base header (8 bits).
+     *
+     * Type: u8.
+     * Maskable: no.
+     * Formatting: decimal.
+     * Prerequisites: NSH.
+     * Access: read/write.
+     * NXM: none.
+     * OXM: OXM_NSH_SI(5) since OF1.3 and v2.8.
+     */
+    MFF_NSH_SI,
+
+    /* "nsh_c1" (aka "nshc1").
+     *
+     * c1 (Network Platform Context) field in NSH context header (32 bits)
+     *
+     * Type: be32.
+     * Maskable: bitwise.
+     * Formatting: hexadecimal.
+     * Prerequisites: NSH.
+     * Access: read/write.
+     * NXM: none.
+     * OXM: OXM_NSH_C1(6) since OF1.3 and v2.8.
+     */
+    MFF_NSH_C1,
+
+    /* "nsh_c2" (aka "nshc2").
+     *
+     * c2 (Network Shared Context) field in NSH context header (32 bits)
+     *
+     * Type: be32.
+     * Maskable: bitwise.
+     * Formatting: hexadecimal.
+     * Prerequisites: NSH.
+     * Access: read/write.
+     * NXM: none.
+     * OXM: OXM_NSH_C2(7) since OF1.3 and v2.8.
+     */
+    MFF_NSH_C2,
+
+    /* "nsh_c3" (aka "nshc3").
+     *
+     * c3 (Service Platform Context) field in NSH context header (32 bits)
+     *
+     * Type: be32.
+     * Maskable: bitwise.
+     * Formatting: hexadecimal.
+     * Prerequisites: NSH.
+     * Access: read/write.
+     * NXM: none.
+     * OXM: OXM_NSH_C3(8) since OF1.3 and v2.8.
+     */
+    MFF_NSH_C3,
+
+    /* "nsh_c4" (aka "nshc4").
+     *
+     * c4 (Service Shared Context) field in NSH context header (32 bits)
+     *
+     * Type: be32.
+     * Maskable: bitwise.
+     * Formatting: hexadecimal.
+     * Prerequisites: NSH.
+     * Access: read/write.
+     * NXM: none.
+     * OXM: OXM_NSH_C4(9) since OF1.3 and v2.8.
+     */
+    MFF_NSH_C4,
+
     MFF_N_IDS
 };
 
@@ -1838,6 +1969,7 @@ enum OVS_PACKED_ENUM mf_prereqs {
     MFP_IPV4,
     MFP_IPV6,
     MFP_IP_ANY,
+    MFP_NSH,
 
     /* L2.5 requirements. */
     MFP_MPLS,
diff --git a/include/openvswitch/nsh.h b/include/openvswitch/nsh.h
new file mode 100644
index 0000000..1593da2
--- /dev/null
+++ b/include/openvswitch/nsh.h
@@ -0,0 +1,130 @@
+#ifndef __OPENVSWITCH_NSH_H
+#define __OPENVSWITCH_NSH_H 1
+
+#include "openvswitch/types.h"
+
+/*
+ * Network Service Header:
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |Ver|O|C|R|R|R|R|R|R|    Length   |   MD Type   |  Next Proto   |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                Service Path ID                | Service Index |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                                                               |
+ * ~               Mandatory/Optional Context Header               ~
+ * |                                                               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * Ver = The version field is used to ensure backward compatibility
+ *       going forward with future NSH updates.  It MUST be set to 0x0
+ *       by the sender, in this first revision of NSH.
+ *
+ * O = OAM. when set to 0x1 indicates that this packet is an operations
+ *     and management (OAM) packet.  The receiving SFF and SFs nodes
+ *     MUST examine the payload and take appropriate action.
+ *
+ * C = context. Indicates that a critical metadata TLV is present.
+ *
+ * Length : total length, in 4-byte words, of NSH including the Base
+ *          Header, the Service Path Header and the optional variable
+ *          TLVs.
+ * MD Type: indicates the format of NSH beyond the mandatory Base Header
+ *          and the Service Path Header.
+ *
+ * Next Protocol: indicates the protocol type of the original packet. A
+ *          new IANA registry will be created for protocol type.
+ *
+ * Service Path Identifier (SPI): identifies a service path.
+ *          Participating nodes MUST use this identifier for Service
+ *          Function Path selection.
+ *
+ * Service Index (SI): provides location within the SFP.
+ *
+ * [0] https://tools.ietf.org/html/draft-ietf-sfc-nsh-13
+ */
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+/**
+ * struct nsh_md1_ctx - Keeps track of NSH context data
+ * @nshc<1-4>: NSH Contexts.
+ */
+struct nsh_md1_ctx {
+    ovs_be32 c1;
+    ovs_be32 c2;
+    ovs_be32 c3;
+    ovs_be32 c4;
+};
+
+struct nsh_md2_tlv {
+    ovs_be16 md_class;
+    uint8_t type;
+    uint8_t length;
+    uint8_t md_value[];
+};
+
+struct nsh_hdr {
+    ovs_be16 ver_flags_len;
+    uint8_t md_type;
+    uint8_t next_proto;
+    ovs_be32 path_hdr;
+    union {
+        struct nsh_md1_ctx md1;
+        struct nsh_md2_tlv md2[0];
+    };
+};
+
+/* Masking NSH header fields. */
+#define NSH_VER_MASK       0xc000
+#define NSH_VER_SHIFT      12
+#define NSH_FLAGS_MASK     0x3fc0
+#define NSH_FLAGS_SHIFT    6
+#define NSH_LEN_MASK       0x003f
+#define NSH_LEN_SHIFT      0
+
+#define NSH_SPI_MASK       0xffffff00
+#define NSH_SPI_SHIFT      8
+#define NSH_SI_MASK        0x000000ff
+#define NSH_SI_SHIFT       0
+
+#define NSH_DST_PORT    4790     /* UDP Port for NSH on VXLAN. */
+#define ETH_P_NSH       0x894F   /* Ethertype for NSH. */
+
+/* NSH Base Header Next Protocol. */
+#define NSH_P_IPV4        0x01
+#define NSH_P_IPV6        0x02
+#define NSH_P_ETHERNET    0x03
+
+/* MD Type Registry. */
+#define NSH_M_TYPE1     0x01
+#define NSH_M_TYPE2     0x02
+#define NSH_M_EXP1      0xFE
+#define NSH_M_EXP2      0xFF
+
+/* sizeof(struct nsh_hdr) + sizeof(struct nsh_md1_ctx). */
+#define NSH_M_TYPE1_LEN  24
+
+static inline uint16_t
+nsh_hdr_len(const struct nsh_hdr *nsh)
+{
+    return 4 * (ntohs(nsh->ver_flags_len) & NSH_LEN_MASK) >> NSH_LEN_SHIFT;
+}
+
+static inline struct nsh_md1_ctx *
+nsh_md1_ctx(struct nsh_hdr *nsh)
+{
+    return &nsh->md1;
+}
+
+static inline struct nsh_md2_tlv *
+nsh_md2_ctx(struct nsh_hdr *nsh)
+{
+    return nsh->md2;
+}
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/openvswitch/packets.h b/include/openvswitch/packets.h
index e440f66..3f40ce5 100644
--- a/include/openvswitch/packets.h
+++ b/include/openvswitch/packets.h
@@ -77,4 +77,23 @@ union flow_vlan_hdr {
 }
 #endif
 
+/* Network Service Header keys */
+struct flow_nsh {
+    uint8_t flags;
+    uint8_t mdtype;
+    uint8_t np;
+    uint8_t si;
+    ovs_be32 spi;
+    ovs_be32 c1;
+    ovs_be32 c2;
+    ovs_be32 c3;
+    ovs_be32 c4;
+};
+
+/* NSH flags */
+#define FLOW_NSH_F_OAM (1 << 0)
+#define FLOW_NSH_F_CTX (1 << 1)
+
+#define FLOW_NSH_F_MASK ((1 << 2) - 1)
+
 #endif /* packets.h */
diff --git a/lib/flow.c b/lib/flow.c
index 2ce3e12..3330073 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -40,6 +40,7 @@
 #include "random.h"
 #include "unaligned.h"
 #include "util.h"
+#include "openvswitch/nsh.h"
 
 COVERAGE_DEFINE(flow_extract);
 COVERAGE_DEFINE(miniflow_malloc);
@@ -125,7 +126,7 @@ struct mf_ctx {
  * away.  Some GCC versions gave warnings on ALWAYS_INLINE, so these are
  * defined as macros. */
 
-#if (FLOW_WC_SEQ != 39)
+#if (FLOW_WC_SEQ != 40)
 #define MINIFLOW_ASSERT(X) ovs_assert(X)
 BUILD_MESSAGE("FLOW_WC_SEQ changed: miniflow_extract() will have runtime "
                "assertions enabled. Consider updating FLOW_WC_SEQ after "
@@ -528,6 +529,55 @@ parse_ipv6_ext_hdrs(const void **datap, size_t *sizep, uint8_t *nw_proto,
     return parse_ipv6_ext_hdrs__(datap, sizep, nw_proto, nw_frag);
 }
 
+int
+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;
+
+    memset(key, 0, sizeof(struct flow_nsh));
+
+    ver_flags_len = ntohs(nsh->ver_flags_len);
+    version = (ver_flags_len & NSH_VER_MASK) >> NSH_VER_SHIFT;
+    flags = (ver_flags_len & NSH_FLAGS_MASK) >> NSH_FLAGS_SHIFT;
+    length = (ver_flags_len & NSH_LEN_MASK) >> NSH_LEN_SHIFT;
+
+    if (version != 0) {
+        return false;
+    }
+
+    key->flags = flags;
+    key->mdtype = nsh->md_type;
+    key->np = nsh->next_proto;
+
+    path_hdr = ntohl(nsh->path_hdr);
+    key->si = (path_hdr & NSH_SI_MASK) >> NSH_SI_SHIFT;
+    key->spi = htonl((path_hdr & NSH_SPI_MASK) >> NSH_SPI_SHIFT);
+
+    switch (key->mdtype) {
+        case NSH_M_TYPE1:
+            if (length != 6) {
+                return -EINVAL;
+            }
+            key->c1 = nsh->md1.c1;
+            key->c2 = nsh->md1.c2;
+            key->c3 = nsh->md1.c3;
+            key->c4 = nsh->md1.c4;
+            break;
+        case NSH_M_TYPE2:
+            /* Don't support MD type 2 yet, so return false */
+        default:
+            return false;
+    }
+
+    /* NSH header length is in 4 byte words. */
+    data_pull(datap, sizep, 4 * length);
+
+    return true;
+}
+
 /* Initializes 'flow' members from 'packet' and 'md', taking the packet type
  * into account.
  *
@@ -817,6 +867,21 @@ miniflow_extract(struct dp_packet *packet, struct miniflow *dst)
                 miniflow_push_macs(mf, arp_sha, arp_buf);
                 miniflow_pad_to_64(mf, arp_tha);
             }
+        } else if (dl_type == htons(ETH_TYPE_NSH)) {
+            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
+                     */
+                }
+            }
         }
         goto out;
     }
@@ -950,7 +1015,7 @@ flow_get_metadata(const struct flow *flow, struct match *flow_metadata)
 {
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 39);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
 
     match_init_catchall(flow_metadata);
     if (flow->tunnel.tun_id != htonll(0)) {
@@ -1514,7 +1579,7 @@ flow_wildcards_init_for_packet(struct flow_wildcards *wc,
     memset(&wc->masks, 0x0, sizeof wc->masks);
 
     /* Update this function whenever struct flow changes. */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 39);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
 
     if (flow_tnl_dst_is_set(&flow->tunnel)) {
         if (flow->tunnel.flags & FLOW_TNL_F_KEY) {
@@ -1613,6 +1678,16 @@ 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.mdtype);
+        WC_MASK_FIELD(wc, nsh.np);
+        WC_MASK_FIELD(wc, nsh.spi);
+        WC_MASK_FIELD(wc, nsh.si);
+        WC_MASK_FIELD(wc, nsh.c1);
+        WC_MASK_FIELD(wc, nsh.c2);
+        WC_MASK_FIELD(wc, nsh.c3);
+        WC_MASK_FIELD(wc, nsh.c4);
     } else {
         return; /* Unknown ethertype. */
     }
@@ -1654,7 +1729,7 @@ void
 flow_wc_map(const struct flow *flow, struct flowmap *map)
 {
     /* Update this function whenever struct flow changes. */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 39);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
 
     flowmap_init(map);
 
@@ -1740,6 +1815,16 @@ flow_wc_map(const struct flow *flow, struct flowmap *map)
         FLOWMAP_SET(map, nw_proto);
         FLOWMAP_SET(map, arp_sha);
         FLOWMAP_SET(map, arp_tha);
+    } else if (flow->dl_type == htons(ETH_TYPE_NSH)) {
+        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.c1);
+        FLOWMAP_SET(map, nsh.c2);
+        FLOWMAP_SET(map, nsh.c3);
+        FLOWMAP_SET(map, nsh.c4);
     }
 }
 
@@ -1749,7 +1834,7 @@ void
 flow_wildcards_clear_non_packet_fields(struct flow_wildcards *wc)
 {
     /* Update this function whenever struct flow changes. */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 39);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
 
     memset(&wc->masks.metadata, 0, sizeof wc->masks.metadata);
     memset(&wc->masks.regs, 0, sizeof wc->masks.regs);
@@ -1893,7 +1978,7 @@ flow_wildcards_set_xxreg_mask(struct flow_wildcards *wc, int idx,
 uint32_t
 miniflow_hash_5tuple(const struct miniflow *flow, uint32_t basis)
 {
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 39);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
     uint32_t hash = basis;
 
     if (flow) {
@@ -1940,7 +2025,7 @@ ASSERT_SEQUENTIAL(ipv6_src, ipv6_dst);
 uint32_t
 flow_hash_5tuple(const struct flow *flow, uint32_t basis)
 {
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 39);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
     uint32_t hash = basis;
 
     if (flow) {
@@ -2529,7 +2614,7 @@ flow_push_mpls(struct flow *flow, int n, ovs_be16 mpls_eth_type,
 
         if (clear_flow_L3) {
             /* Clear all L3 and L4 fields and dp_hash. */
-            BUILD_ASSERT(FLOW_WC_SEQ == 39);
+            BUILD_ASSERT(FLOW_WC_SEQ == 40);
             memset((char *) flow + FLOW_SEGMENT_2_ENDS_AT, 0,
                    sizeof(struct flow) - FLOW_SEGMENT_2_ENDS_AT);
             flow->dp_hash = 0;
diff --git a/lib/flow.h b/lib/flow.h
index 0c6069c..75face1 100644
--- a/lib/flow.h
+++ b/lib/flow.h
@@ -129,6 +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);
+int parse_nsh(const void **datap, size_t *sizep, struct flow_nsh *key);
 
 static inline uint64_t
 flow_get_xreg(const struct flow *flow, int idx)
@@ -914,7 +915,7 @@ static inline void
 pkt_metadata_from_flow(struct pkt_metadata *md, const struct flow *flow)
 {
     /* Update this function whenever struct flow changes. */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 39);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
 
     md->recirc_id = flow->recirc_id;
     md->dp_hash = flow->dp_hash;
diff --git a/lib/match.c b/lib/match.c
index f5288e3..f287df1 100644
--- a/lib/match.c
+++ b/lib/match.c
@@ -24,6 +24,7 @@
 #include "openvswitch/ofp-util.h"
 #include "packets.h"
 #include "tun-metadata.h"
+#include "openvswitch/nsh.h"
 
 /* Converts the flow in 'flow' into a match in 'match', with the given
  * 'wildcards'. */
@@ -1089,6 +1090,21 @@ format_ipv6_netmask(struct ds *s, const char *name,
 }
 
 static void
+format_uint8_masked(struct ds *s, const char *name,
+                   uint8_t value, uint8_t mask)
+{
+    if (mask != 0) {
+        ds_put_format(s, "%s%s=%s", colors.param, name, colors.end);
+        if (mask == UINT8_MAX) {
+            ds_put_format(s, "%"PRIu8, value);
+        } else {
+            ds_put_format(s, "0x%02"PRIx8"/0x%02"PRIx8, value, mask);
+        }
+        ds_put_char(s, ',');
+    }
+}
+
+static void
 format_uint16_masked(struct ds *s, const char *name,
                    uint16_t value, uint16_t mask)
 {
@@ -1128,7 +1144,23 @@ format_be32_masked(struct ds *s, const char *name,
         if (mask == OVS_BE32_MAX) {
             ds_put_format(s, "%"PRIu32, ntohl(value));
         } else {
-            ds_put_format(s, "0x%"PRIx32"/0x%"PRIx32,
+            ds_put_format(s, "0x%08"PRIx32"/0x%08"PRIx32,
+                          ntohl(value), ntohl(mask));
+        }
+        ds_put_char(s, ',');
+    }
+}
+
+static void
+format_be32_masked_hex(struct ds *s, const char *name,
+                       ovs_be32 value, ovs_be32 mask)
+{
+    if (mask != htonl(0)) {
+        ds_put_format(s, "%s%s=%s", colors.param, name, colors.end);
+        if (mask == OVS_BE32_MAX) {
+            ds_put_format(s, "0x%08"PRIx32, ntohl(value));
+        } else {
+            ds_put_format(s, "0x%08"PRIx32"/0x%08"PRIx32,
                           ntohl(value), ntohl(mask));
         }
         ds_put_char(s, ',');
@@ -1218,6 +1250,39 @@ 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)
+{
+    if (m->nsh.flags) {
+        format_uint8_masked(s, "nsh_flags", f->nsh.flags, m->nsh.flags);
+    }
+    if (m->nsh.mdtype) {
+        format_uint8_masked(s, "nsh_mdtype", f->nsh.mdtype, m->nsh.mdtype);
+    }
+    if (m->nsh.np) {
+        format_uint8_masked(s, "nsh_np", f->nsh.np, m->nsh.np);
+    }
+    if (m->nsh.spi) {
+        format_be32_masked(s, "nsh_spi", f->nsh.spi, m->nsh.spi);
+    }
+    if (m->nsh.si) {
+        format_uint8_masked(s, "nsh_si", f->nsh.si, m->nsh.si);
+    }
+
+    if (m->nsh.c1) {
+        format_be32_masked_hex(s, "nsh_c1", f->nsh.c1, m->nsh.c1);
+    }
+    if (m->nsh.c2) {
+        format_be32_masked_hex(s, "nsh_c2", f->nsh.c2, m->nsh.c2);
+    }
+    if (m->nsh.c3) {
+        format_be32_masked_hex(s, "nsh_c3", f->nsh.c3, m->nsh.c3);
+    }
+    if (m->nsh.c4) {
+        format_be32_masked_hex(s, "nsh_c4", f->nsh.c4, m->nsh.c4);
+    }
+}
+
 /* Appends a string representation of 'match' to 's'.  If 'priority' is
  * different from OFP_DEFAULT_PRIORITY, includes it in 's'.  If 'port_map' is
  * nonnull, uses it to translate port numbers to names in output. */
@@ -1235,7 +1300,7 @@ match_format(const struct match *match,
 
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 39);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
 
     if (priority != OFP_DEFAULT_PRIORITY) {
         ds_put_format(s, "%spriority=%s%d,",
@@ -1321,7 +1386,6 @@ match_format(const struct match *match,
     }
 
     if (wc->masks.dl_type) {
-        dl_type = f->dl_type;
         skip_type = true;
         if (dl_type == htons(ETH_TYPE_IP)) {
             if (wc->masks.nw_proto) {
@@ -1459,6 +1523,8 @@ match_format(const struct match *match,
                dl_type == htons(ETH_TYPE_RARP)) {
         format_ip_netmask(s, "arp_spa", f->nw_src, wc->masks.nw_src);
         format_ip_netmask(s, "arp_tpa", f->nw_dst, wc->masks.nw_dst);
+    } else if (dl_type == htons(ETH_TYPE_NSH)) {
+        format_nsh_masked(s, f, &wc->masks);
     } else {
         format_ip_netmask(s, "nw_src", f->nw_src, wc->masks.nw_src);
         format_ip_netmask(s, "nw_dst", f->nw_dst, wc->masks.nw_dst);
diff --git a/lib/meta-flow.c b/lib/meta-flow.c
index d098081..53c8ed0 100644
--- a/lib/meta-flow.c
+++ b/lib/meta-flow.c
@@ -359,6 +359,25 @@ mf_is_all_wild(const struct mf_field *mf, const struct flow_wildcards *wc)
     case MFF_TCP_FLAGS:
         return !wc->masks.tcp_flags;
 
+    case MFF_NSH_FLAGS:
+        return !wc->masks.nsh.flags;
+    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;
+    case MFF_NSH_SI:
+        return !wc->masks.nsh.si;
+    case MFF_NSH_C1:
+        return !wc->masks.nsh.c1;
+    case MFF_NSH_C2:
+        return !wc->masks.nsh.c2;
+    case MFF_NSH_C3:
+        return !wc->masks.nsh.c3;
+    case MFF_NSH_C4:
+        return !wc->masks.nsh.c4;
+
     case MFF_N_IDS:
     default:
         OVS_NOT_REACHED();
@@ -423,6 +442,8 @@ mf_are_prereqs_ok__(const struct mf_field *mf, const struct flow *flow,
         return eth_type_mpls(dl_type);
     case MFP_IP_ANY:
         return is_ip_any(flow);
+    case MFP_NSH:
+        return dl_type == htons(ETH_TYPE_NSH);
     case MFP_CT_VALID:
         return is_ct_valid(flow, mask, wc);
     case MFP_TCP:
@@ -586,6 +607,17 @@ mf_is_value_valid(const struct mf_field *mf, const union mf_value *value)
     case MFF_CT_STATE:
         return !(value->be32 & ~htonl(CS_SUPPORTED_MASK));
 
+    case MFF_NSH_FLAGS:
+    case MFF_NSH_MDTYPE:
+    case MFF_NSH_NP:
+    case MFF_NSH_SPI:
+    case MFF_NSH_SI:
+    case MFF_NSH_C1:
+    case MFF_NSH_C2:
+    case MFF_NSH_C3:
+    case MFF_NSH_C4:
+        return true;
+
     case MFF_N_IDS:
     default:
         OVS_NOT_REACHED();
@@ -863,6 +895,34 @@ mf_get_value(const struct mf_field *mf, const struct flow *flow,
         value->ipv6 = flow->nd_target;
         break;
 
+    case MFF_NSH_FLAGS:
+        value->u8 = flow->nsh.flags;
+        break;
+    case MFF_NSH_MDTYPE:
+        value->u8 = flow->nsh.mdtype;
+        break;
+    case MFF_NSH_NP:
+        value->u8 = flow->nsh.np;
+        break;
+    case MFF_NSH_SPI:
+        value->be32 = flow->nsh.spi;
+        break;
+    case MFF_NSH_SI:
+        value->u8 = flow->nsh.si;
+        break;
+    case MFF_NSH_C1:
+        value->be32 = flow->nsh.c1;
+        break;
+    case MFF_NSH_C2:
+        value->be32 = flow->nsh.c2;
+        break;
+    case MFF_NSH_C3:
+        value->be32 = flow->nsh.c3;
+        break;
+    case MFF_NSH_C4:
+        value->be32 = flow->nsh.c4;
+        break;
+
     case MFF_N_IDS:
     default:
         OVS_NOT_REACHED();
@@ -1156,6 +1216,34 @@ mf_set_value(const struct mf_field *mf,
         match_set_nd_target(match, &value->ipv6);
         break;
 
+    case MFF_NSH_FLAGS:
+        MATCH_SET_FIELD_UINT8(match, nsh.flags, value->u8);
+        break;
+    case MFF_NSH_MDTYPE:
+        MATCH_SET_FIELD_UINT8(match, nsh.mdtype, value->u8);
+        break;
+    case MFF_NSH_NP:
+        MATCH_SET_FIELD_UINT8(match, nsh.np, value->u8);
+        break;
+    case MFF_NSH_SPI:
+        MATCH_SET_FIELD_BE32(match, nsh.spi, value->be32);
+        break;
+    case MFF_NSH_SI:
+        MATCH_SET_FIELD_UINT8(match, nsh.si, value->u8);
+        break;
+    case MFF_NSH_C1:
+        MATCH_SET_FIELD_BE32(match, nsh.c1, value->be32);
+        break;
+    case MFF_NSH_C2:
+        MATCH_SET_FIELD_BE32(match, nsh.c2, value->be32);
+        break;
+    case MFF_NSH_C3:
+        MATCH_SET_FIELD_BE32(match, nsh.c3, value->be32);
+        break;
+    case MFF_NSH_C4:
+        MATCH_SET_FIELD_BE32(match, nsh.c4, value->be32);
+        break;
+
     case MFF_N_IDS:
     default:
         OVS_NOT_REACHED();
@@ -1525,6 +1613,34 @@ mf_set_flow_value(const struct mf_field *mf,
         flow->nd_target = value->ipv6;
         break;
 
+    case MFF_NSH_FLAGS:
+        flow->nsh.flags = value->u8;
+        break;
+    case MFF_NSH_MDTYPE:
+        flow->nsh.mdtype = value->u8;
+        break;
+    case MFF_NSH_NP:
+        flow->nsh.np = value->u8;
+        break;
+    case MFF_NSH_SPI:
+        flow->nsh.spi = value->be32;
+        break;
+    case MFF_NSH_SI:
+        flow->nsh.si = value->u8;
+        break;
+    case MFF_NSH_C1:
+        flow->nsh.c1 = value->be32;
+        break;
+    case MFF_NSH_C2:
+        flow->nsh.c2 = value->be32;
+        break;
+    case MFF_NSH_C3:
+        flow->nsh.c3 = value->be32;
+        break;
+    case MFF_NSH_C4:
+        flow->nsh.c4 = value->be32;
+        break;
+
     case MFF_N_IDS:
     default:
         OVS_NOT_REACHED();
@@ -1651,6 +1767,15 @@ mf_is_pipeline_field(const struct mf_field *mf)
     case MFF_ND_TARGET:
     case MFF_ND_SLL:
     case MFF_ND_TLL:
+    case MFF_NSH_FLAGS:
+    case MFF_NSH_MDTYPE:
+    case MFF_NSH_NP:
+    case MFF_NSH_SPI:
+    case MFF_NSH_SI:
+    case MFF_NSH_C1:
+    case MFF_NSH_C2:
+    case MFF_NSH_C3:
+    case MFF_NSH_C4:
         return false;
 
     case MFF_N_IDS:
@@ -1985,6 +2110,34 @@ mf_set_wild(const struct mf_field *mf, struct match *match, char **err_str)
         memset(&match->flow.nd_target, 0, sizeof match->flow.nd_target);
         break;
 
+    case MFF_NSH_FLAGS:
+        MATCH_SET_FIELD_MASKED(match, nsh.flags, 0, 0);
+        break;
+    case MFF_NSH_MDTYPE:
+        MATCH_SET_FIELD_MASKED(match, nsh.mdtype, 0, 0);
+        break;
+    case MFF_NSH_NP:
+        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));
+        break;
+    case MFF_NSH_SI:
+        MATCH_SET_FIELD_MASKED(match, nsh.si, 0, 0);
+        break;
+    case MFF_NSH_C1:
+        MATCH_SET_FIELD_MASKED(match, nsh.c1, htonl(0), htonl(0));
+        break;
+    case MFF_NSH_C2:
+        MATCH_SET_FIELD_MASKED(match, nsh.c2, htonl(0), htonl(0));
+        break;
+    case MFF_NSH_C3:
+        MATCH_SET_FIELD_MASKED(match, nsh.c3, htonl(0), htonl(0));
+        break;
+    case MFF_NSH_C4:
+        MATCH_SET_FIELD_MASKED(match, nsh.c4, htonl(0), htonl(0));
+        break;
+
     case MFF_N_IDS:
     default:
         OVS_NOT_REACHED();
@@ -2222,6 +2375,34 @@ mf_set(const struct mf_field *mf,
         match_set_tcp_flags_masked(match, value->be16, mask->be16);
         break;
 
+    case MFF_NSH_FLAGS:
+        MATCH_SET_FIELD_MASKED(match, nsh.flags, value->u8, mask->u8);
+        break;
+    case MFF_NSH_MDTYPE:
+        MATCH_SET_FIELD_MASKED(match, nsh.mdtype, value->u8, mask->u8);
+        break;
+    case MFF_NSH_NP:
+        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);
+        break;
+    case MFF_NSH_SI:
+        MATCH_SET_FIELD_MASKED(match, nsh.si, value->u8, mask->u8);
+        break;
+    case MFF_NSH_C1:
+        MATCH_SET_FIELD_MASKED(match, nsh.c1, value->be32, mask->be32);
+        break;
+    case MFF_NSH_C2:
+        MATCH_SET_FIELD_MASKED(match, nsh.c2, value->be32, mask->be32);
+        break;
+    case MFF_NSH_C3:
+        MATCH_SET_FIELD_MASKED(match, nsh.c3, value->be32, mask->be32);
+        break;
+    case MFF_NSH_C4:
+        MATCH_SET_FIELD_MASKED(match, nsh.c4, value->be32, mask->be32);
+        break;
+
     case MFF_N_IDS:
     default:
         OVS_NOT_REACHED();
diff --git a/lib/meta-flow.xml b/lib/meta-flow.xml
index 634ab69..b634c51 100644
--- a/lib/meta-flow.xml
+++ b/lib/meta-flow.xml
@@ -1292,6 +1292,27 @@ tcp,tp_src=0x07c0/0xfff0
     </field>
   </group>
 
+  <group title="Network Service Header">
+    <field id="MFF_NSH_FLAGS"
+        title="flags field (8 bits)"/>
+    <field id="MFF_NSH_MDTYPE"
+        title="mdtype field (8 bits)"/>
+    <field id="MFF_NSH_NP"
+        title="np (next protocol) field (8 bits)"/>
+    <field id="MFF_NSH_SPI"
+        title="spi (service path identifier) field (24 bits)"/>
+    <field id="MFF_NSH_SI"
+        title="si (service index) field (8 bits)"/>
+    <field id="MFF_NSH_C1"
+        title="c1 (Network Platform Context) field (32 bits)"/>
+    <field id="MFF_NSH_C2"
+        title="c2 (Network Shared Context) field (32 bits)"/>
+    <field id="MFF_NSH_C3"
+        title="c3 (Service Platform Context) field (32 bits)"/>
+    <field id="MFF_NSH_C4"
+        title="c4 (Service Shared Context) field (32 bits)"/>
+  </group>
+
   <group title="Tunnel">
     <p>
       The fields in this group relate to tunnels, which Open vSwitch
diff --git a/lib/nx-match.c b/lib/nx-match.c
index 571755a..8326f2e 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -1025,7 +1025,7 @@ nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, const struct match *match,
     int match_len;
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 39);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
 
     struct nxm_put_ctx ctx = { .output = b, .implied_ethernet = false };
 
@@ -1154,6 +1154,25 @@ nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, const struct match *match,
                flow->tunnel.gbp_flags, match->wc.masks.tunnel.gbp_flags);
     tun_metadata_to_nx_match(b, oxm, 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_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);
+    nxm_put_32m(&ctx, MFF_NSH_C1, oxm, flow->nsh.c1,
+            match->wc.masks.nsh.c1);
+    nxm_put_32m(&ctx, MFF_NSH_C2, oxm, flow->nsh.c2,
+            match->wc.masks.nsh.c2);
+    nxm_put_32m(&ctx, MFF_NSH_C3, oxm, flow->nsh.c3,
+            match->wc.masks.nsh.c3);
+    nxm_put_32m(&ctx, MFF_NSH_C4, oxm, flow->nsh.c4,
+            match->wc.masks.nsh.c4);
+
     /* Registers. */
     if (oxm < OFP15_VERSION) {
         for (i = 0; i < FLOW_N_REGS; i++) {
diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index 03120bf..7d7d787 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -295,6 +295,8 @@ 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:
+        break;
     case OVS_KEY_ATTR_IPV4:
         ipv4_key = nl_attr_get_unspec(a, sizeof(struct ovs_key_ipv4));
         packet_set_ipv4(packet, ipv4_key->ipv4_src,
@@ -419,6 +421,8 @@ odp_execute_masked_set_action(struct dp_packet *packet,
                           get_mask(a, struct ovs_key_ethernet));
         break;
 
+    case OVS_KEY_ATTR_NSH:
+        break;
     case OVS_KEY_ATTR_IPV4:
         odp_set_ipv4(packet, nl_attr_get(a),
                      get_mask(a, struct ovs_key_ipv4));
diff --git a/lib/odp-util.c b/lib/odp-util.c
index babed75..78821dc 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -175,6 +175,7 @@ ovs_key_attr_to_string(enum ovs_key_attr attr, char *namebuf, size_t bufsize)
     case OVS_KEY_ATTR_DP_HASH: return "dp_hash";
     case OVS_KEY_ATTR_RECIRC_ID: return "recirc_id";
     case OVS_KEY_ATTR_PACKET_TYPE: return "packet_type";
+    case OVS_KEY_ATTR_NSH: return "nsh";
 
     case __OVS_KEY_ATTR_MAX:
     default:
@@ -247,6 +248,99 @@ format_odp_clone_action(struct ds *ds, const struct nlattr *attr,
     ds_put_format(ds, ")");
 }
 
+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, ",mdtype=%d", key->mdtype);
+    ds_put_format(ds, ",np=%d", key->np);
+    ds_put_format(ds, ",spi=%d",
+                  (ntohl(key->path_hdr) & NSH_SPI_MASK) >> NSH_SPI_SHIFT);
+    ds_put_format(ds, ",si=%d",
+                  (ntohl(key->path_hdr) & NSH_SI_MASK) >> NSH_SI_SHIFT);
+
+    switch (key->mdtype) {
+        case NSH_M_TYPE1:
+            ds_put_format(ds, ",c1=0x%08x", ntohl(key->c1));
+            ds_put_format(ds, ",c2=0x%08x", ntohl(key->c2));
+            ds_put_format(ds, ",c3=0x%08x", ntohl(key->c3));
+            ds_put_format(ds, ",c4=0x%08x", ntohl(key->c4));
+            break;
+        case NSH_M_TYPE2:
+            /* TODO */
+            break;
+        default:
+            OVS_NOT_REACHED();
+    }
+}
+
+static void
+format_uint8_masked(struct ds *s, bool *first, const char *name,
+                    uint8_t value, uint8_t mask)
+{
+    if (mask != 0) {
+        if (!*first) {
+            ds_put_char(s, ',');
+        }
+        ds_put_format(s, "%s=", name);
+        if (mask == UINT8_MAX) {
+            ds_put_format(s, "%"PRIu8, value);
+        } else {
+            ds_put_format(s, "0x%02"PRIx8"/0x%02"PRIx8, value, mask);
+        }
+        *first = false;
+    }
+}
+
+static void
+format_be32_masked(struct ds *s, bool *first, const char *name,
+                   ovs_be32 value, ovs_be32 mask)
+{
+    if (mask != htonl(0)) {
+        if (!*first) {
+            ds_put_char(s, ',');
+        }
+        ds_put_format(s, "%s=", name);
+        if (mask == OVS_BE32_MAX) {
+            ds_put_format(s, "0x%08"PRIx32, ntohl(value));
+        } else {
+            ds_put_format(s, "0x%08"PRIx32"/0x%08"PRIx32,
+                          ntohl(value), ntohl(mask));
+        }
+        *first = false;
+    }
+}
+
+static void
+format_nsh_key_mask(struct ds *ds, const struct ovs_key_nsh *key,
+                    const struct ovs_key_nsh *mask)
+{
+    if (!mask) {
+        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) {
+            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;
+
+        format_uint8_masked(ds, &first, "flags", key->flags, mask->flags);
+        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));
+        format_uint8_masked(ds, &first, "si", si, si_mask);
+        format_be32_masked(ds, &first, "c1", key->c1, mask->c1);
+        format_be32_masked(ds, &first, "c2", key->c2, mask->c2);
+        format_be32_masked(ds, &first, "c3", key->c3, mask->c3);
+        format_be32_masked(ds, &first, "c4", key->c4, mask->c4);
+    }
+}
+
 static const char *
 slow_path_reason_to_string(uint32_t reason)
 {
@@ -1970,6 +2064,7 @@ static const struct attr_len_tbl ovs_flow_key_attr_lens[OVS_KEY_ATTR_MAX + 1] =
     [OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4] = { .len = sizeof(struct ovs_key_ct_tuple_ipv4) },
     [OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6] = { .len = sizeof(struct ovs_key_ct_tuple_ipv6) },
     [OVS_KEY_ATTR_PACKET_TYPE] = { .len = 4  },
+    [OVS_KEY_ATTR_NSH]       = { .len = sizeof(struct ovs_key_nsh) },
 };
 
 /* Returns the correct length of the payload for a flow key attribute of the
@@ -2222,6 +2317,7 @@ odp_mask_is_constant__(enum ovs_key_attr attr, const void *mask, size_t size,
     case OVS_KEY_ATTR_CT_MARK:
     case OVS_KEY_ATTR_CT_LABELS:
     case OVS_KEY_ATTR_PACKET_TYPE:
+    case OVS_KEY_ATTR_NSH:
         return is_all_byte(mask, size, u8);
 
     case OVS_KEY_ATTR_TCP_FLAGS:
@@ -3188,6 +3284,12 @@ format_odp_key_attr__(const struct nlattr *a, const struct nlattr *ma,
         ds_chomp(ds, ',');
         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);
+        break;
+    }
     case OVS_KEY_ATTR_UNSPEC:
     case __OVS_KEY_ATTR_MAX:
     default:
@@ -3606,6 +3708,29 @@ scan_be16(const char *s, ovs_be16 *key, ovs_be16 *mask)
 }
 
 static int
+scan_be32(const char *s, ovs_be32 *key, ovs_be32 *mask)
+{
+    uint32_t key_, mask_;
+    int n;
+
+    if (ovs_scan(s, "%"SCNi32"%n", &key_, &n)) {
+        int len = n;
+
+        *key = htonl(key_);
+        if (mask) {
+            if (ovs_scan(s + len, "/%"SCNi32"%n", &mask_, &n)) {
+                len += n;
+                *mask = htonl(mask_);
+            } else {
+                *mask = OVS_BE32_MAX;
+            }
+        }
+        return len;
+    }
+    return 0;
+}
+
+static int
 scan_be64(const char *s, ovs_be64 *key, ovs_be64 *mask)
 {
     uint64_t key_, mask_;
@@ -4406,6 +4531,17 @@ 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, c1);
+        SCAN_FIELD("c2=", be32, c2);
+        SCAN_FIELD("c3=", be32, c3);
+        SCAN_FIELD("c4=", be32, c4);
+    } SCAN_END(OVS_KEY_ATTR_NSH);
+
     /* Encap open-coded. */
     if (!strncmp(s, "encap(", 6)) {
         const char *start = s;
@@ -4514,6 +4650,10 @@ static void get_arp_key(const struct flow *, struct ovs_key_arp *);
 static void put_arp_key(const struct ovs_key_arp *, struct flow *);
 static void get_nd_key(const struct flow *, struct ovs_key_nd *);
 static void put_nd_key(const struct ovs_key_nd *, struct flow *);
+static void get_nsh_key(const struct flow *flow, struct ovs_key_nsh *nsh,
+                        bool is_mask);
+static void put_nsh_key(const struct ovs_key_nsh *nsh, struct flow *flow,
+                        bool is_mask);
 
 /* These share the same layout. */
 union ovs_key_tp {
@@ -4690,6 +4830,12 @@ odp_flow_key_from_flow__(const struct odp_flow_key_parms *parms,
         for (i = 0; i < n; i++) {
             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);
     }
 
     if (is_ip_any(flow) && !(flow->nw_frag & FLOW_NW_FRAG_LATER)) {
@@ -4933,6 +5079,7 @@ odp_key_to_dp_packet(const struct nlattr *key, size_t key_len,
         case OVS_KEY_ATTR_TCP_FLAGS:
         case OVS_KEY_ATTR_MPLS:
         case OVS_KEY_ATTR_PACKET_TYPE:
+        case OVS_KEY_ATTR_NSH:
         case __OVS_KEY_ATTR_MAX:
         default:
             break;
@@ -5243,6 +5390,21 @@ parse_l2_5_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
                 expected_bit = OVS_KEY_ATTR_ARP;
             }
         }
+    } else if (src_flow->dl_type == htons(ETH_TYPE_NSH)) {
+        if (!is_mask) {
+            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);
+            if (is_mask) {
+                check_start = nsh_key;
+                check_len = sizeof *nsh_key;
+                expected_bit = OVS_KEY_ATTR_NSH;
+            }
+        }
     } else {
         goto done;
     }
@@ -6285,6 +6447,61 @@ commit_set_nw_action(const struct flow *flow, struct flow *base,
     return 0;
 }
 
+static 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) {
+        nsh->c1 = flow->nsh.c1;
+        nsh->c2 = flow->nsh.c2;
+        nsh->c3 = flow->nsh.c3;
+        nsh->c4 = flow->nsh.c4;
+    } else {
+        switch (nsh->mdtype) {
+        case NSH_M_TYPE1:
+            nsh->c1 = flow->nsh.c1;
+            nsh->c2 = flow->nsh.c2;
+            nsh->c3 = flow->nsh.c3;
+            nsh->c4 = flow->nsh.c4;
+            break;
+        case NSH_M_TYPE2:
+            /* TODO: MD type 2 */
+            break;
+        }
+    }
+}
+
+static 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:
+            flow->nsh.c1 = nsh->c1;
+            flow->nsh.c2 = nsh->c2;
+            flow->nsh.c3 = nsh->c3;
+            flow->nsh.c4 = nsh->c4;
+            break;
+        case NSH_M_TYPE2:
+            /* TODO: MD type 2 */
+            flow->nsh.c1 = 0;
+            flow->nsh.c2 = 0;
+            flow->nsh.c3 = 0;
+            flow->nsh.c4 = 0;
+            break;
+    }
+}
+
 /* TCP, UDP, and SCTP keys have the same layout. */
 BUILD_ASSERT_DECL(sizeof(struct ovs_key_tcp) == sizeof(struct ovs_key_udp) &&
                   sizeof(struct ovs_key_tcp) == sizeof(struct ovs_key_sctp));
diff --git a/lib/odp-util.h b/lib/odp-util.h
index bdb2354..111cd9c 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -147,7 +147,7 @@ void odp_portno_name_format(const struct hmap *portno_names,
  * add another field and forget to adjust this value.
  */
 #define ODPUTIL_FLOW_KEY_BYTES 640
-BUILD_ASSERT_DECL(FLOW_WC_SEQ == 39);
+BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
 
 /* A buffer with sufficient size and alignment to hold an nlattr-formatted flow
  * key.  An array of "struct nlattr" might not, in theory, be sufficiently
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index 354a6ce..86dd5cb 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -102,7 +102,7 @@ ofputil_netmask_to_wcbits(ovs_be32 netmask)
 void
 ofputil_wildcard_from_ofpfw10(uint32_t ofpfw, struct flow_wildcards *wc)
 {
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 39);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
 
     /* Initialize most of wc. */
     flow_wildcards_init_catchall(wc);
diff --git a/lib/packets.h b/lib/packets.h
index fabab08..e8b09e5 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -25,6 +25,7 @@
 #include "openvswitch/geneve.h"
 #include "openvswitch/packets.h"
 #include "openvswitch/types.h"
+#include "openvswitch/nsh.h"
 #include "odp-netlink.h"
 #include "random.h"
 #include "hash.h"
@@ -378,6 +379,7 @@ ovs_be32 set_mpls_lse_values(uint8_t ttl, uint8_t tc, uint8_t bos,
 #define ETH_TYPE_RARP          0x8035
 #define ETH_TYPE_MPLS          0x8847
 #define ETH_TYPE_MPLS_MCAST    0x8848
+#define ETH_TYPE_NSH           0x894f
 
 static inline bool eth_type_mpls(ovs_be16 eth_type)
 {
@@ -1279,6 +1281,7 @@ enum packet_type {
     PT_IPV6 = PACKET_TYPE(OFPHTN_ETHERTYPE, ETH_TYPE_IPV6),
     PT_MPLS = PACKET_TYPE(OFPHTN_ETHERTYPE, ETH_TYPE_MPLS),
     PT_MPLS_MC = PACKET_TYPE(OFPHTN_ETHERTYPE, ETH_TYPE_MPLS_MCAST),
+    PT_NSH  = PACKET_TYPE(OFPHTN_ETHERTYPE, ETH_TYPE_NSH),
     PT_UNKNOWN = PACKET_TYPE(0xffff, 0xffff),   /* Unknown packet type. */
 };
 
diff --git a/ofproto/ofproto-dpif-rid.h b/ofproto/ofproto-dpif-rid.h
index 45b2336..19fc27c 100644
--- a/ofproto/ofproto-dpif-rid.h
+++ b/ofproto/ofproto-dpif-rid.h
@@ -99,7 +99,7 @@ struct rule;
 /* Metadata for restoring pipeline context after recirculation.  Helpers
  * are inlined below to keep them together with the definition for easier
  * updates. */
-BUILD_ASSERT_DECL(FLOW_WC_SEQ == 39);
+BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
 
 struct frozen_metadata {
     /* Metadata in struct flow. */
diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
index fc665a6..e4cca65 100644
--- a/ofproto/ofproto-dpif-sflow.c
+++ b/ofproto/ofproto-dpif-sflow.c
@@ -1050,6 +1050,7 @@ sflow_read_set_action(const struct nlattr *attr,
     case OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6:
     case OVS_KEY_ATTR_UNSPEC:
     case OVS_KEY_ATTR_PACKET_TYPE:
+    case OVS_KEY_ATTR_NSH:
     case __OVS_KEY_ATTR_MAX:
     default:
         break;
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 2f36305..dc389e6 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -3728,7 +3728,7 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
 
     /* If 'struct flow' gets additional metadata, we'll need to zero it out
      * before traversing a patch port. */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 39);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
     memset(&flow_tnl, 0, sizeof flow_tnl);
 
     if (!check_output_prerequisites(ctx, xport, flow, check_stp)) {
diff --git a/tests/ofproto.at b/tests/ofproto.at
index 9c10253..31cb520 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -2386,7 +2386,7 @@ head_table () {
         actions: output group set_field strip_vlan push_vlan mod_nw_ttl dec_ttl set_mpls_ttl dec_mpls_ttl push_mpls pop_mpls set_queue
         supported on Set-Field: tun_id tun_src tun_dst tun_ipv6_src tun_ipv6_dst tun_flags tun_gbp_id tun_gbp_flags tun_metadata0 dnl
 tun_metadata1 tun_metadata2 tun_metadata3 tun_metadata4 tun_metadata5 tun_metadata6 tun_metadata7 tun_metadata8 tun_metadata9 tun_metadata10 tun_metadata11 tun_metadata12 tun_metadata13 tun_metadata14 tun_metadata15 tun_metadata16 tun_metadata17 tun_metadata18 tun_metadata19 tun_metadata20 tun_metadata21 tun_metadata22 tun_metadata23 tun_metadata24 tun_metadata25 tun_metadata26 tun_metadata27 tun_metadata28 tun_metadata29 tun_metadata30 tun_metadata31 tun_metadata32 tun_metadata33 tun_metadata34 tun_metadata35 tun_metadata36 tun_metadata37 tun_metadata38 tun_metadata39 tun_metadata40 tun_metadata41 tun_metadata42 tun_metadata43 tun_metadata44 tun_metadata45 tun_metadata46 tun_metadata47 tun_metadata48 tun_metadata49 tun_metadata50 tun_metadata51 tun_metadata52 tun_metadata53 tun_metadata54 tun_metadata55 tun_metadata56 tun_metadata57 tun_metadata58 tun_metadata59 tun_metadata60 tun_metadata61 tun_metadata62 tun_metadata63 dnl
-metadata in_port in_port_oxm pkt_mark ct_mark ct_label reg0 reg1 reg2 reg3 reg4 reg5 reg6 reg7 reg8 reg9 reg10 reg11 reg12 reg13 reg14 reg15 xreg0 xreg1 xreg2 xreg3 xreg4 xreg5 xreg6 xreg7 xxreg0 xxreg1 xxreg2 xxreg3 eth_src eth_dst vlan_tci vlan_vid vlan_pcp mpls_label mpls_tc mpls_ttl ip_src ip_dst ipv6_src ipv6_dst ipv6_label nw_tos ip_dscp nw_ecn nw_ttl arp_op arp_spa arp_tpa arp_sha arp_tha tcp_src tcp_dst udp_src udp_dst sctp_src sctp_dst icmp_type icmp_code icmpv6_type icmpv6_code nd_target nd_sll nd_tll
+metadata in_port in_port_oxm pkt_mark ct_mark ct_label reg0 reg1 reg2 reg3 reg4 reg5 reg6 reg7 reg8 reg9 reg10 reg11 reg12 reg13 reg14 reg15 xreg0 xreg1 xreg2 xreg3 xreg4 xreg5 xreg6 xreg7 xxreg0 xxreg1 xxreg2 xxreg3 eth_src eth_dst vlan_tci vlan_vid vlan_pcp mpls_label mpls_tc mpls_ttl ip_src ip_dst ipv6_src ipv6_dst ipv6_label nw_tos ip_dscp nw_ecn nw_ttl arp_op arp_spa arp_tpa arp_sha arp_tha tcp_src tcp_dst udp_src udp_dst sctp_src sctp_dst icmp_type icmp_code icmpv6_type icmpv6_code nd_target nd_sll nd_tll nsh_flags nsh_spi nsh_si nsh_c1 nsh_c2 nsh_c3 nsh_c4
     matching:
       dp_hash: arbitrary mask
       recirc_id: exact match or wildcard
@@ -2548,6 +2548,15 @@ metadata in_port in_port_oxm pkt_mark ct_mark ct_label reg0 reg1 reg2 reg3 reg4
       nd_target: arbitrary mask
       nd_sll: arbitrary mask
       nd_tll: arbitrary mask
+      nsh_flags: arbitrary mask
+      nsh_mdtype: exact match or wildcard
+      nsh_np: exact match or wildcard
+      nsh_spi: exact match or wildcard
+      nsh_si: exact match or wildcard
+      nsh_c1: arbitrary mask
+      nsh_c2: arbitrary mask
+      nsh_c3: arbitrary mask
+      nsh_c4: arbitrary mask
 
 ' $1
 }
