diff --git a/lib/flow.c b/lib/flow.c
index 091ebf0..d285080 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -531,7 +531,7 @@ 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);
 }
 
-static int
+int
 parse_nsh(const void **datap, size_t *sizep, struct flow_nsh *key)
 {
     const struct nsh_hdr *nsh = (const struct nsh_hdr *) *datap;
@@ -543,6 +543,10 @@ parse_nsh(const void **datap, size_t *sizep, struct flow_nsh *key)
     if (length > NSH_LEN_MAX)
         return -EINVAL;
 
+    key->flags = (nsh->base.version << 6) &
+                 (nsh->base.oam_flag << 5) &
+                 (nsh->base.context_flag << 4) &
+                 nsh->base.reserved_flags1;
     key->mdtype = nsh->base.md_type;
     key->np = nsh->base.next_proto;
     key->si = nsh->base.svc_idx;
diff --git a/lib/flow.h b/lib/flow.h
index d29dcb1..fc499dc 100644
--- a/lib/flow.h
+++ b/lib/flow.h
@@ -120,6 +120,7 @@ void flow_compose(struct dp_packet *, const struct flow *);
 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)
diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index 0891607..6b9a642 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -203,6 +203,20 @@ odp_set_tunnel_action(const struct nlattr *a, struct flow_tnl *tun_key)
 }
 
 static void
+odp_set_nsh(struct dp_packet *packet, const struct nlattr *a, const struct flow_nsh *mask)
+{
+    struct flow_nsh key;
+    enum odp_key_fitness fitness;
+
+    const void *data = dp_packet_l3(packet);
+    size_t size = dp_packet_size(packet);
+    parse_nsh(&data, &size, &key);
+    fitness = odp_nsh_key_from_attr(a, &key);
+    ovs_assert(fitness != ODP_FIT_ERROR);
+    packet_set_nsh(packet, &key, mask);
+}
+
+static void
 set_arp(struct dp_packet *packet, const struct ovs_key_arp *key,
         const struct ovs_key_arp *mask)
 {
@@ -296,7 +310,7 @@ odp_execute_set_action(struct dp_packet *packet, const struct nlattr *a)
         break;
 
     case OVS_KEY_ATTR_NSH:
-        /* TODO */
+        odp_set_nsh(packet, a, NULL);
         break;
 
     case OVS_KEY_ATTR_IPV4:
@@ -424,7 +438,7 @@ odp_execute_masked_set_action(struct dp_packet *packet,
         break;
 
     case OVS_KEY_ATTR_NSH:
-        /* TODO */
+        odp_set_nsh(packet, a, get_mask(a, struct flow_nsh));
         break;
 
     case OVS_KEY_ATTR_IPV4:
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 3a685df..476bc8b 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -41,7 +41,6 @@
 #include "util.h"
 #include "uuid.h"
 #include "openvswitch/vlog.h"
-#include "openvswitch/nsh.h"
 
 VLOG_DEFINE_THIS_MODULE(odp_util);
 
@@ -2181,44 +2180,81 @@ tun_key_to_attr(struct ofpbuf *a, const struct flow_tnl *tun_key,
 }
 
 static void
-nsh_key_to_attr(struct ofpbuf *a, const struct flow *key)
+nsh_key_to_attr(struct ofpbuf *a, const struct flow *base, 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) {
+    if (base->nsh.flags != key->nsh.flags) {
         nl_msg_put_u8(a, OVS_NSH_KEY_ATTR_FLAGS, key->nsh.flags);
     }
-    if (key->nsh.mdtype) {
+    if (base->nsh.mdtype != key->nsh.mdtype) {
         nl_msg_put_u8(a, OVS_NSH_KEY_ATTR_MDTYPE, key->nsh.mdtype);
     }
-    if (key->nsh.np) {
+    if (base->nsh.np != key->nsh.np) {
         nl_msg_put_u8(a, OVS_NSH_KEY_ATTR_NP, key->nsh.np);
     }
-    if (key->nsh.spi) {
+    if (base->nsh.spi != key->nsh.spi) {
         nl_msg_put_be32(a, OVS_NSH_KEY_ATTR_SPI, key->nsh.spi);
     }
-    if (key->nsh.si) {
+    if (base->nsh.si != key->nsh.si) {
         nl_msg_put_u8(a, OVS_NSH_KEY_ATTR_SI, key->nsh.si);
     }
-    if (key->nsh.c1) {
+    if (base->nsh.c1 != key->nsh.c1) {
         nl_msg_put_be32(a, OVS_NSH_KEY_ATTR_C1, key->nsh.c1);
     }
-    if (key->nsh.c2) {
+    if (base->nsh.c2 != key->nsh.c2) {
         nl_msg_put_be32(a, OVS_NSH_KEY_ATTR_C2, key->nsh.c2);
     }
-    if (key->nsh.c3) {
+    if (base->nsh.c3 != key->nsh.c3) {
         nl_msg_put_be32(a, OVS_NSH_KEY_ATTR_C3, key->nsh.c3);
     }
-    if (key->nsh.c4) {
+    if (base->nsh.c4 != 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)
+static void
+nsh_key_to_attr_masked(struct ofpbuf *a, const struct flow *base, const struct flow *key, const struct flow *mask)
+{
+    size_t nsh_key_ofs = 0;
+
+    nsh_key_ofs = nl_msg_start_nested(a, OVS_KEY_ATTR_NSH);
+
+    if (base->nsh.flags != key->nsh.flags) {
+        nl_msg_put_u8(a, OVS_NSH_KEY_ATTR_FLAGS, key->nsh.flags & mask->nsh.flags);
+    }
+    if (base->nsh.mdtype != key->nsh.mdtype) {
+        nl_msg_put_u8(a, OVS_NSH_KEY_ATTR_MDTYPE, key->nsh.mdtype & mask->nsh.mdtype);
+    }
+    if (base->nsh.np != key->nsh.np) {
+        nl_msg_put_u8(a, OVS_NSH_KEY_ATTR_NP, key->nsh.np & mask->nsh.np);
+    }
+    if (base->nsh.spi != key->nsh.spi) {
+        nl_msg_put_be32(a, OVS_NSH_KEY_ATTR_SPI, key->nsh.spi & mask->nsh.spi);
+    }
+    if (base->nsh.si != key->nsh.si) {
+        nl_msg_put_u8(a, OVS_NSH_KEY_ATTR_SI, key->nsh.si & mask->nsh.si);
+    }
+    if (base->nsh.c1 != key->nsh.c1) {
+        nl_msg_put_be32(a, OVS_NSH_KEY_ATTR_C1, key->nsh.c1 & mask->nsh.c1);
+    }
+    if (base->nsh.c2 != key->nsh.c2) {
+        nl_msg_put_be32(a, OVS_NSH_KEY_ATTR_C2, key->nsh.c2 & mask->nsh.c2);
+    }
+    if (base->nsh.c3 != key->nsh.c3) {
+        nl_msg_put_be32(a, OVS_NSH_KEY_ATTR_C3, key->nsh.c3 & mask->nsh.c3);
+    }
+    if (base->nsh.c4 != key->nsh.c4) {
+        nl_msg_put_be32(a, OVS_NSH_KEY_ATTR_C4, key->nsh.c4 & mask->nsh.c4);
+    }
+    nl_msg_end_nested(a, nsh_key_ofs);
+}
+
+enum odp_key_fitness
+odp_nsh_key_from_attr(const struct nlattr *attr, struct flow_nsh *key)
 {
     unsigned int left;
     const struct nlattr *a;
@@ -2236,31 +2272,31 @@ odp_nsh_key_from_attr(const struct nlattr *attr, struct flow *key)
 
         switch (type) {
         case OVS_NSH_KEY_ATTR_FLAGS:
-            key->nsh.flags = nl_attr_get_u8(a);
+            key->flags = nl_attr_get_u8(a);
             break;
         case OVS_NSH_KEY_ATTR_MDTYPE:
-            key->nsh.mdtype = nl_attr_get_u8(a);
+            key->mdtype = nl_attr_get_u8(a);
             break;
         case OVS_NSH_KEY_ATTR_NP:
-            key->nsh.np = nl_attr_get_u8(a);
+            key->np = nl_attr_get_u8(a);
             break;
         case OVS_NSH_KEY_ATTR_SPI:
-            key->nsh.spi = nl_attr_get_be32(a);
+            key->spi = nl_attr_get_be32(a);
             break;
         case OVS_NSH_KEY_ATTR_SI:
-            key->nsh.si = nl_attr_get_u8(a);
+            key->si = nl_attr_get_u8(a);
             break;
         case OVS_NSH_KEY_ATTR_C1:
-            key->nsh.c1 = nl_attr_get_be32(a);
+            key->c1 = nl_attr_get_be32(a);
             break;
         case OVS_NSH_KEY_ATTR_C2:
-            key->nsh.c2 = nl_attr_get_be32(a);
+            key->c2 = nl_attr_get_be32(a);
             break;
         case OVS_NSH_KEY_ATTR_C3:
-            key->nsh.c3 = nl_attr_get_be32(a);
+            key->c3 = nl_attr_get_be32(a);
             break;
         case OVS_NSH_KEY_ATTR_C4:
-            key->nsh.c4 = nl_attr_get_be32(a);
+            key->c4 = nl_attr_get_be32(a);
             break;
         default:
             /* Allow this to show up as unexpected, if there are unknown
@@ -2304,6 +2340,18 @@ odp_mask_is_exact(enum ovs_key_attr attr, const void *mask, size_t size)
     if (attr == OVS_KEY_ATTR_TUNNEL) {
         return false;
     }
+    if (attr == OVS_KEY_ATTR_NSH) {
+        const struct flow_nsh *nsh_mask = mask;
+        return (((nsh_mask->flags == 0) || (nsh_mask->flags == UINT8_MAX))
+                && ((nsh_mask->mdtype == 0) || (nsh_mask->mdtype == UINT8_MAX))
+                && ((nsh_mask->np == 0) || (nsh_mask->np == UINT8_MAX))
+                && ((nsh_mask->spi == 0) || (nsh_mask->spi == OVS_BE32_MAX))
+                && ((nsh_mask->si == 0) || (nsh_mask->si == UINT8_MAX))
+                && ((nsh_mask->c1 == 0) || (nsh_mask->c1 == OVS_BE32_MAX))
+                && ((nsh_mask->c2 == 0) || (nsh_mask->c2 == OVS_BE32_MAX))
+                && ((nsh_mask->c3 == 0) || (nsh_mask->c3 == OVS_BE32_MAX))
+                && ((nsh_mask->c4 == 0) || (nsh_mask->c4 == OVS_BE32_MAX)));
+    }
 
     if (attr == OVS_KEY_ATTR_ARP) {
         /* ARP key has padding, ignore it. */
@@ -2903,11 +2951,6 @@ format_odp_nsh_attr(const struct nlattr *attr, const struct nlattr *mask_attr,
         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,
@@ -4899,7 +4942,7 @@ odp_flow_key_from_flow__(const struct odp_flow_key_parms *parms,
             mpls_key[i].mpls_lse = data->mpls_lse[i];
         }
     } else if (flow->dl_type == htons(ETH_TYPE_NSH)) {
-        nsh_key_to_attr(buf, data);
+        nsh_key_to_attr(buf, flow, data);
     }
 
     if (is_ip_any(flow) && !(flow->nw_frag & FLOW_NW_FRAG_LATER)) {
@@ -5471,7 +5514,7 @@ parse_l2_5_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
         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);
+            res = odp_nsh_key_from_attr(attrs[OVS_KEY_ATTR_NSH], &flow->nsh);
             if (res == ODP_FIT_ERROR) {
                 return ODP_FIT_ERROR;
             } else if (res == ODP_FIT_PERFECT) {
@@ -6582,6 +6625,30 @@ commit_set_pkt_mark_action(const struct flow *flow, struct flow *base_flow,
     }
 }
 
+static void
+commit_set_nsh_action(const struct flow *flow, struct flow *base_flow,
+                      struct ofpbuf *odp_actions,
+                      struct flow_wildcards *wc,
+                      bool use_masked)
+{
+    if (!memcmp(&base_flow->nsh, &flow->nsh, sizeof base_flow->nsh)) {
+        return;
+    }
+
+    bool fully_masked = odp_mask_is_exact(OVS_KEY_ATTR_NSH, &wc->masks.nsh, sizeof(struct flow_nsh));
+
+    if (use_masked && !fully_masked) {
+        size_t offset = nl_msg_start_nested(odp_actions, OVS_ACTION_ATTR_SET_MASKED);
+        nsh_key_to_attr_masked(odp_actions, base_flow, flow, &wc->masks);
+        nl_msg_end_nested(odp_actions, offset);
+    } else {
+        size_t offset = nl_msg_start_nested(odp_actions, OVS_ACTION_ATTR_SET);
+        nsh_key_to_attr(odp_actions, base_flow, flow);
+        nl_msg_end_nested(odp_actions, offset);
+    }
+    memcpy(&base_flow->nsh, &flow->nsh, sizeof base_flow->nsh);
+}
+
 /* If any of the flow key data that ODP actions can modify are different in
  * 'base' and 'flow', appends ODP actions to 'odp_actions' that change the flow
  * key from 'base' into 'flow', and then changes 'base' the same way.  Does not
@@ -6606,6 +6673,7 @@ commit_odp_actions(const struct flow *flow, struct flow *base,
         commit_mpls_action(flow, base, odp_actions);
         mpls_done = true;
     }
+    commit_set_nsh_action(flow, base, odp_actions, wc, use_masked);
     slow1 = commit_set_nw_action(flow, base, odp_actions, wc, use_masked);
     commit_set_port_action(flow, base, odp_actions, wc, use_masked);
     slow2 = commit_set_icmp_action(flow, base, odp_actions, wc);
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 1d7dcc4..f167eaf 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -155,6 +155,9 @@ 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 *attr,
+                                           struct flow_nsh *key);
+
 int odp_ufid_from_string(const char *s_, ovs_u128 *ufid);
 void odp_format_ufid(const ovs_u128 *ufid, struct ds *);
 
diff --git a/lib/packets.c b/lib/packets.c
index 3bf5e94..9467768 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -267,6 +267,43 @@ pop_eth(struct dp_packet *packet)
     packet->packet_type = PACKET_TYPE_BE(OFPHTN_ETHERTYPE, htons(ethertype));
 }
 
+void packet_set_nsh(struct dp_packet *packet, const struct flow_nsh * nsh_key, const struct flow_nsh * nsh_mask)
+{
+    struct nsh_hdr * nsh_header = (struct nsh_hdr *)dp_packet_l3(packet);
+    struct nsh_md1_ctx * md1_ctx = nsh_md1_ctx(nsh_header);
+
+    if (!nsh_mask) {
+        nsh_header->base.reserved_flags1 = nsh_key->flags & 0x0F;
+        nsh_header->base.context_flag = (nsh_key->flags & 0x10) >> 4;
+        nsh_header->base.oam_flag = (nsh_key->flags & 0x20) >> 5;
+        nsh_header->base.version = (nsh_key->flags & 0xC0) >> 6;
+        nsh_header->base.md_type = nsh_key->mdtype;
+        nsh_header->base.next_proto = nsh_key->np;
+        nsh_header->base.path_hdr = (nsh_key->spi >> 8) | (nsh_key->si << 24);
+        md1_ctx->c1 = nsh_key->c1;
+        md1_ctx->c2 = nsh_key->c2;
+        md1_ctx->c3 = nsh_key->c3;
+        md1_ctx->c4 = nsh_key->c4;
+    } else {
+        nsh_header->base.reserved_flags1 = ~nsh_mask->flags &
+                                           nsh_key->flags & 0x0F;
+        nsh_header->base.context_flag = (~nsh_mask->flags &
+                                         nsh_key->flags & 0x10) >> 4;
+        nsh_header->base.oam_flag = (~nsh_mask->flags &
+                                     nsh_key->flags & 0x20) >> 5;
+        nsh_header->base.version = (~nsh_mask->flags &
+                                    nsh_key->flags & 0xC0) >> 6;
+        nsh_header->base.md_type = ~nsh_mask->mdtype & nsh_key->mdtype;
+        nsh_header->base.next_proto = ~nsh_mask->np & nsh_key->np;
+        nsh_header->base.path_hdr = ((~nsh_mask->spi & nsh_key->spi) >> 8) |
+                                    ((~nsh_mask->si & nsh_key->si) << 24);
+        md1_ctx->c1 = ~nsh_mask->c1 & nsh_key->c1;
+        md1_ctx->c2 = ~nsh_mask->c2 & nsh_key->c2;
+        md1_ctx->c3 = ~nsh_mask->c3 & nsh_key->c3;
+        md1_ctx->c4 = ~nsh_mask->c4 & nsh_key->c4;
+    }
+}
+
 /* Set ethertype of the packet. */
 static void
 set_ethertype(struct dp_packet *packet, ovs_be16 eth_type)
diff --git a/lib/packets.h b/lib/packets.h
index 34d56b4..e271246 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"
@@ -1198,6 +1199,7 @@ void packet_set_sctp_port(struct dp_packet *, ovs_be16 src, ovs_be16 dst);
 void packet_set_icmp(struct dp_packet *, uint8_t type, uint8_t code);
 void packet_set_nd(struct dp_packet *, const struct in6_addr *target,
                    const struct eth_addr sll, const struct eth_addr tll);
+void packet_set_nsh(struct dp_packet *packet, const struct flow_nsh * nsh_key, const struct flow_nsh *nsh_mask);
 
 void packet_format_tcp_flags(struct ds *, uint16_t);
 const char *packet_tcp_flag_to_string(uint32_t flag);
