diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h
index 0146d23..dd1a76e 100644
--- a/datapath/linux/compat/include/linux/openvswitch.h
+++ b/datapath/linux/compat/include/linux/openvswitch.h
@@ -359,6 +359,7 @@ 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 */
+	OVS_KEY_ATTR_NSH,	/* Nested set of ovs_nsh attributes */
 
 #ifdef __KERNEL__
 	/* Only used within kernel data path. */
@@ -884,4 +885,19 @@ enum ovs_action_attr {
 
 #define OVS_ACTION_ATTR_MAX (__OVS_ACTION_ATTR_MAX - 1)
 
+enum ovs_nsh_key_attr {
+	OVS_NSH_KEY_ATTR_FLAGS,             /* u8 NSH header flags */
+	OVS_NSH_KEY_ATTR_MDTYPE,            /* u8 Metadata Type */
+	OVS_NSH_KEY_ATTR_NP,                /* u8 Next Protocol */
+	OVS_NSH_KEY_ATTR_SPI,               /* be32 Service Path ID */
+	OVS_NSH_KEY_ATTR_SI,                /* u8 Service Index */
+	OVS_NSH_KEY_ATTR_C1,                /* be32 NSH Context Header C1 */
+	OVS_NSH_KEY_ATTR_C2,                /* be32 NSH Context Header C2 */
+	OVS_NSH_KEY_ATTR_C3,                /* be32 NSH Context Header C3 */
+	OVS_NSH_KEY_ATTR_C4,                /* be32 NSH Context Header C4 */
+	__OVS_NSH_KEY_ATTR_MAX
+};
+
+#define OVS_NSH_KEY_ATTR_MAX (__OVS_NSH_KEY_ATTR_MAX - 1)
+
 #endif /* _LINUX_OPENVSWITCH_H */
diff --git a/lib/flow.c b/lib/flow.c
index 0500852..091ebf0 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -1673,6 +1673,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);
     }
 }
 
diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index 0b59707..0891607 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -295,6 +295,10 @@ 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:
+        /* TODO */
+        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 +423,10 @@ odp_execute_masked_set_action(struct dp_packet *packet,
                           get_mask(a, struct ovs_key_ethernet));
         break;
 
+    case OVS_KEY_ATTR_NSH:
+        /* TODO */
+        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 5445314..3a685df 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -41,6 +41,7 @@
 #include "util.h"
 #include "uuid.h"
 #include "openvswitch/vlog.h"
+#include "openvswitch/nsh.h"
 
 VLOG_DEFINE_THIS_MODULE(odp_util);
 
@@ -174,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:
@@ -1920,6 +1922,18 @@ static const struct attr_len_tbl ovs_tun_key_attr_lens[OVS_TUNNEL_KEY_ATTR_MAX +
     [OVS_TUNNEL_KEY_ATTR_IPV6_DST]      = { .len = 16 },
 };
 
+static const struct attr_len_tbl ovs_nsh_key_attr_lens[OVS_NSH_KEY_ATTR_MAX + 1] = {
+    [OVS_NSH_KEY_ATTR_FLAGS]            = { .len = 1 },
+    [OVS_NSH_KEY_ATTR_MDTYPE]           = { .len = 1 },
+    [OVS_NSH_KEY_ATTR_NP]               = { .len = 1 },
+    [OVS_NSH_KEY_ATTR_SPI]              = { .len = 4 },
+    [OVS_NSH_KEY_ATTR_SI]               = { .len = 1 },
+    [OVS_NSH_KEY_ATTR_C1]               = { .len = 4 },
+    [OVS_NSH_KEY_ATTR_C2]               = { .len = 4 },
+    [OVS_NSH_KEY_ATTR_C3]               = { .len = 4 },
+    [OVS_NSH_KEY_ATTR_C4]               = { .len = 4 },
+};
+
 static const struct attr_len_tbl ovs_flow_key_attr_lens[OVS_KEY_ATTR_MAX + 1] = {
     [OVS_KEY_ATTR_ENCAP]     = { .len = ATTR_LEN_NESTED },
     [OVS_KEY_ATTR_PRIORITY]  = { .len = 4 },
@@ -1951,6 +1965,9 @@ static const struct attr_len_tbl ovs_flow_key_attr_lens[OVS_KEY_ATTR_MAX + 1] =
     [OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4] = { .len = sizeof(struct ovs_key_ct_tuple_ipv4) },
     [OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6] = { .len = sizeof(struct ovs_key_ct_tuple_ipv6) },
     [OVS_KEY_ATTR_PACKET_TYPE] = { .len = 4  },
+    [OVS_KEY_ATTR_NSH]       = { .len = ATTR_LEN_NESTED,
+                                 .next = ovs_nsh_key_attr_lens,
+                                 .next_max = OVS_NSH_KEY_ATTR_MAX },
 };
 
 /* Returns the correct length of the payload for a flow key attribute of the
@@ -2163,6 +2180,102 @@ tun_key_to_attr(struct ofpbuf *a, const struct flow_tnl *tun_key,
     nl_msg_end_nested(a, tun_key_ofs);
 }
 
+static void
+nsh_key_to_attr(struct ofpbuf *a, const struct flow *key)
+{
+    size_t nsh_key_ofs = 0;
+
+    nsh_key_ofs = nl_msg_start_nested(a, OVS_KEY_ATTR_NSH);
+
+    if (key->nsh.flags) {
+        nl_msg_put_u8(a, OVS_NSH_KEY_ATTR_FLAGS, key->nsh.flags);
+    }
+    if (key->nsh.mdtype) {
+        nl_msg_put_u8(a, OVS_NSH_KEY_ATTR_MDTYPE, key->nsh.mdtype);
+    }
+    if (key->nsh.np) {
+        nl_msg_put_u8(a, OVS_NSH_KEY_ATTR_NP, key->nsh.np);
+    }
+    if (key->nsh.spi) {
+        nl_msg_put_be32(a, OVS_NSH_KEY_ATTR_SPI, key->nsh.spi);
+    }
+    if (key->nsh.si) {
+        nl_msg_put_u8(a, OVS_NSH_KEY_ATTR_SI, key->nsh.si);
+    }
+    if (key->nsh.c1) {
+        nl_msg_put_be32(a, OVS_NSH_KEY_ATTR_C1, key->nsh.c1);
+    }
+    if (key->nsh.c2) {
+        nl_msg_put_be32(a, OVS_NSH_KEY_ATTR_C2, key->nsh.c2);
+    }
+    if (key->nsh.c3) {
+        nl_msg_put_be32(a, OVS_NSH_KEY_ATTR_C3, key->nsh.c3);
+    }
+    if (key->nsh.c4) {
+        nl_msg_put_be32(a, OVS_NSH_KEY_ATTR_C4, key->nsh.c4);
+    }
+    nl_msg_end_nested(a, nsh_key_ofs);
+}
+
+static enum odp_key_fitness
+odp_nsh_key_from_attr(const struct nlattr *attr, struct flow *key)
+{
+    unsigned int left;
+    const struct nlattr *a;
+    bool unknown = false;
+
+    NL_NESTED_FOR_EACH(a, left, attr) {
+        uint16_t type = nl_attr_type(a);
+        size_t len = nl_attr_get_size(a);
+        int expected_len = odp_key_attr_len(ovs_nsh_key_attr_lens,
+                                            OVS_NSH_KEY_ATTR_MAX, type);
+
+        if (len != expected_len && expected_len >= 0) {
+            return ODP_FIT_ERROR;
+        }
+
+        switch (type) {
+        case OVS_NSH_KEY_ATTR_FLAGS:
+            key->nsh.flags = nl_attr_get_u8(a);
+            break;
+        case OVS_NSH_KEY_ATTR_MDTYPE:
+            key->nsh.mdtype = nl_attr_get_u8(a);
+            break;
+        case OVS_NSH_KEY_ATTR_NP:
+            key->nsh.np = nl_attr_get_u8(a);
+            break;
+        case OVS_NSH_KEY_ATTR_SPI:
+            key->nsh.spi = nl_attr_get_be32(a);
+            break;
+        case OVS_NSH_KEY_ATTR_SI:
+            key->nsh.si = nl_attr_get_u8(a);
+            break;
+        case OVS_NSH_KEY_ATTR_C1:
+            key->nsh.c1 = nl_attr_get_be32(a);
+            break;
+        case OVS_NSH_KEY_ATTR_C2:
+            key->nsh.c2 = nl_attr_get_be32(a);
+            break;
+        case OVS_NSH_KEY_ATTR_C3:
+            key->nsh.c3 = nl_attr_get_be32(a);
+            break;
+        case OVS_NSH_KEY_ATTR_C4:
+            key->nsh.c4 = nl_attr_get_be32(a);
+            break;
+        default:
+            /* Allow this to show up as unexpected, if there are unknown
+             * NSH attribute, eventually resulting in ODP_FIT_TOO_MUCH. */
+            unknown = true;
+            break;
+        }
+    }
+
+    if (unknown) {
+        return ODP_FIT_TOO_MUCH;
+    }
+    return ODP_FIT_PERFECT;
+}
+
 static bool
 odp_mask_attr_is_wildcard(const struct nlattr *ma)
 {
@@ -2280,6 +2393,23 @@ format_eth(struct ds *ds, const char *name, const struct eth_addr key,
 }
 
 static void
+format_be32(struct ds *ds, const char *name, ovs_be32 key,
+            const ovs_be32 *mask, bool verbose)
+{
+    bool mask_empty = mask && !*mask;
+
+    if (verbose || !mask_empty) {
+        bool mask_full = !mask || *mask == OVS_BE32_MAX;
+
+        ds_put_format(ds, "%s=0x%08"PRIx32, name, ntohl(key));
+        if (!mask_full) { /* Partially masked. */
+            ds_put_format(ds, "/0x%08"PRIx32, ntohl(*mask));
+        }
+        ds_put_char(ds, ',');
+    }
+}
+
+static void
 format_be64(struct ds *ds, const char *name, ovs_be64 key,
             const ovs_be64 *mask, bool verbose)
 {
@@ -2757,6 +2887,82 @@ format_odp_tun_attr(const struct nlattr *attr, const struct nlattr *mask_attr,
     ofpbuf_uninit(&ofp);
 }
 
+static void
+format_odp_nsh_attr(const struct nlattr *attr, const struct nlattr *mask_attr,
+                    struct ds *ds, bool verbose)
+{
+    unsigned int left;
+    const struct nlattr *a;
+    struct ofpbuf ofp;
+
+    ofpbuf_init(&ofp, 100);
+    NL_NESTED_FOR_EACH(a, left, attr) {
+        enum ovs_nsh_key_attr type = nl_attr_type(a);
+        const struct nlattr *ma = NULL;
+
+        if (mask_attr) {
+            ma = nl_attr_find__(nl_attr_get(mask_attr),
+                                nl_attr_get_size(mask_attr), type);
+            if (!ma) {
+                ma = generate_all_wildcard_mask(ovs_nsh_key_attr_lens,
+                                                OVS_NSH_KEY_ATTR_MAX,
+                                                &ofp, a);
+            }
+        }
+
+        if (!check_attr_len(ds, a, ma, ovs_nsh_key_attr_lens,
+                            OVS_NSH_KEY_ATTR_MAX, true)) {
+            continue;
+        }
+
+        switch (type) {
+        case OVS_NSH_KEY_ATTR_FLAGS:
+            format_u8x(ds, "flags", nl_attr_get_u8(a),
+                        ma ? nl_attr_get(ma) : NULL, verbose);
+            break;
+        case OVS_NSH_KEY_ATTR_MDTYPE:
+            format_u8u(ds, "mdtype", nl_attr_get_u8(a),
+                        ma ? nl_attr_get(ma) : NULL, verbose);
+            break;
+        case OVS_NSH_KEY_ATTR_NP:
+            format_u8u(ds, "np", nl_attr_get_u8(a),
+                        ma ? nl_attr_get(ma) : NULL, verbose);
+            break;
+        case OVS_NSH_KEY_ATTR_SPI:
+            format_be32(ds, "spi", nl_attr_get_be32(a),
+                        ma ? nl_attr_get(ma) : NULL, verbose);
+            break;
+        case OVS_NSH_KEY_ATTR_SI:
+            format_u8x(ds, "si", nl_attr_get_u8(a),
+                        ma ? nl_attr_get(ma) : NULL, verbose);
+            break;
+        case OVS_NSH_KEY_ATTR_C1:
+            format_be32(ds, "c1", nl_attr_get_be32(a),
+                        ma ? nl_attr_get(ma) : NULL, verbose);
+            break;
+        case OVS_NSH_KEY_ATTR_C2:
+            format_be32(ds, "c2", nl_attr_get_be32(a),
+                        ma ? nl_attr_get(ma) : NULL, verbose);
+            break;
+        case OVS_NSH_KEY_ATTR_C3:
+            format_be32(ds, "c3", nl_attr_get_be32(a),
+                        ma ? nl_attr_get(ma) : NULL, verbose);
+            break;
+        case OVS_NSH_KEY_ATTR_C4:
+            format_be32(ds, "c4", nl_attr_get_be32(a),
+                        ma ? nl_attr_get(ma) : NULL, verbose);
+            break;
+        case __OVS_NSH_KEY_ATTR_MAX:
+        default:
+            format_unknown_key(ds, a, ma);
+        }
+        ofpbuf_clear(&ofp);
+    }
+
+    ds_chomp(ds, ',');
+    ofpbuf_uninit(&ofp);
+}
+
 static const char *
 odp_ct_state_to_string(uint32_t flag)
 {
@@ -3122,6 +3328,9 @@ format_odp_key_attr(const struct nlattr *a, const struct nlattr *ma,
         ds_chomp(ds, ',');
         break;
     }
+    case OVS_KEY_ATTR_NSH:
+        format_odp_nsh_attr(a, ma, ds, verbose);
+        break;
     case OVS_KEY_ATTR_UNSPEC:
     case __OVS_KEY_ATTR_MAX:
     default:
@@ -4351,6 +4560,18 @@ parse_odp_key_mask_attr(const char *s, const struct simap *port_names,
         SCAN_FIELD("tll=", eth, nd_tll);
     } SCAN_END(OVS_KEY_ATTR_ND);
 
+    SCAN_BEGIN_NESTED("nsh(", OVS_KEY_ATTR_NSH) {
+        SCAN_FIELD_NESTED("flags=", uint8_t, u8, OVS_NSH_KEY_ATTR_FLAGS);
+        SCAN_FIELD_NESTED("mdtype=", uint8_t, u8, OVS_NSH_KEY_ATTR_MDTYPE);
+        SCAN_FIELD_NESTED("np=", uint8_t, u8, OVS_NSH_KEY_ATTR_NP);
+        SCAN_FIELD_NESTED("spi=", uint32_t, u32, OVS_NSH_KEY_ATTR_SPI);
+        SCAN_FIELD_NESTED("si=", uint8_t, u8, OVS_NSH_KEY_ATTR_SI);
+        SCAN_FIELD_NESTED("c1=", uint32_t, u32, OVS_NSH_KEY_ATTR_C1);
+        SCAN_FIELD_NESTED("c2=", uint32_t, u32, OVS_NSH_KEY_ATTR_C2);
+        SCAN_FIELD_NESTED("c3=", uint32_t, u32, OVS_NSH_KEY_ATTR_C3);
+        SCAN_FIELD_NESTED("c4=", uint32_t, u32, OVS_NSH_KEY_ATTR_C4);
+    } SCAN_END_NESTED();
+
     /* Encap open-coded. */
     if (!strncmp(s, "encap(", 6)) {
         const char *start = s;
@@ -4677,6 +4898,8 @@ 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)) {
+        nsh_key_to_attr(buf, data);
     }
 
     if (is_ip_any(flow) && !(flow->nw_frag & FLOW_NW_FRAG_LATER)) {
@@ -5241,6 +5464,20 @@ 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)) {
+            enum odp_key_fitness res;
+
+            res = odp_nsh_key_from_attr(attrs[OVS_KEY_ATTR_NSH], flow);
+            if (res == ODP_FIT_ERROR) {
+                return ODP_FIT_ERROR;
+            } else if (res == ODP_FIT_PERFECT) {
+                expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_NSH;
+            }
+        }
     } else {
         goto done;
     }
diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
index d9fddb1..4c7e362 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;
