diff mbox

[ovs-dev,v2,5/6] Generic encap and decap support for NSH

Message ID 1501707417-99701-6-git-send-email-yi.y.yang@intel.com
State Changes Requested
Headers show

Commit Message

Yang, Yi Aug. 2, 2017, 8:56 p.m. UTC
From: Jan Scheurich <jan.scheurich@ericsson.com>

This commit adds translation and netdev datapath support for generic
encap and decap actions for the NSH MD1 header. The generic encap and
decap actions are mapped to specific encap_nsh and decap_nsh actions
in the datapath.

The translation follows that general scheme that decap() of an NSH
packet triggers recirculation after decapsulation, while encap(nsh)
just modifies struct flow and sets the ctx->pending_encap flag to
generate the encap_nsh action at the next commit to be able to include
subsequent set_field actions for NSH headers.

Support for the flexible MD2 format using TLV properties is foreseen
in encap(nsh), but not yet fully implemented.

The CLI syntax for encap of NSH is
encap(hdr=nsh,prop(class=nsh,type=md_type,val=1))
encap(hdr=nsh,prop(class=nsh,type=md_type,val=2),
      prop(class=nsh,type=tlv,val(<tlv_class>,<tlv_type>,<hex_string>)))

Signed-off-by: Jan Scheurich <jan.scheurich@ericsson.com>
Signed-off-by: Yi Yang <yi.y.yang@intel.com>
---
 datapath/linux/compat/include/linux/openvswitch.h |  23 ++
 include/openvswitch/nsh.h                         |  12 +-
 include/openvswitch/ofp-ed-props.h                |  44 +++-
 lib/dpif-netdev.c                                 |   2 +
 lib/dpif.c                                        |   2 +
 lib/match.c                                       |  24 +-
 lib/odp-execute.c                                 |  36 ++-
 lib/odp-util.c                                    | 277 ++++++++++++++++++++--
 lib/odp-util.h                                    |   3 +-
 lib/ofp-actions.c                                 |   5 +
 lib/ofp-ed-props.c                                | 179 ++++++++++++++
 lib/packets.c                                     |  82 +++++++
 lib/packets.h                                     |   4 +
 ofproto/ofproto-dpif-sflow.c                      |   2 +
 ofproto/ofproto-dpif-xlate.c                      | 179 +++++++++++++-
 tests/nsh.at                                      |  12 +-
 16 files changed, 822 insertions(+), 64 deletions(-)

Comments

Ben Pfaff Aug. 2, 2017, 10:38 p.m. UTC | #1
On Thu, Aug 03, 2017 at 04:56:56AM +0800, Yi Yang wrote:
> From: Jan Scheurich <jan.scheurich@ericsson.com>
> 
> This commit adds translation and netdev datapath support for generic
> encap and decap actions for the NSH MD1 header. The generic encap and
> decap actions are mapped to specific encap_nsh and decap_nsh actions
> in the datapath.
> 
> The translation follows that general scheme that decap() of an NSH
> packet triggers recirculation after decapsulation, while encap(nsh)
> just modifies struct flow and sets the ctx->pending_encap flag to
> generate the encap_nsh action at the next commit to be able to include
> subsequent set_field actions for NSH headers.
> 
> Support for the flexible MD2 format using TLV properties is foreseen
> in encap(nsh), but not yet fully implemented.
> 
> The CLI syntax for encap of NSH is
> encap(hdr=nsh,prop(class=nsh,type=md_type,val=1))
> encap(hdr=nsh,prop(class=nsh,type=md_type,val=2),
>       prop(class=nsh,type=tlv,val(<tlv_class>,<tlv_type>,<hex_string>)))
> Signed-off-by: Jan Scheurich <jan.scheurich@ericsson.com>
> Signed-off-by: Yi Yang <yi.y.yang@intel.com>

The syntax examples above should be updated.

Some checkpatch warnings worth addressing here:

    ERROR: Inappropriate bracing around statement
    #215 FILE: lib/match.c:1268:
            if (m->nsh.c1)

    ERROR: Inappropriate bracing around statement
    #217 FILE: lib/match.c:1270:
            if (m->nsh.c2)

    ERROR: Inappropriate bracing around statement
    #219 FILE: lib/match.c:1272:
            if (m->nsh.c3)

    ERROR: Inappropriate bracing around statement
    #221 FILE: lib/match.c:1274:
            if (m->nsh.c4)

    WARNING: Line length is >79-characters long
    #323 FILE: lib/odp-util.c:259:
        ds_put_format(ds, ",spi=0x%x", (ntohl(key->path_hdr) & NSH_SPI_MASK) >> NSH_SPI_SHIFT);

    WARNING: Line length is >79-characters long
    #612 FILE: lib/odp-util.c:6809:
        encap_nsh.path_hdr = htonl((ntohl(flow->nsh.spi) << NSH_SPI_SHIFT) | flow->nsh.si);

    ERROR: Improper whitespace around control block
    #1026 FILE: lib/packets.c:412:
        switch(ntohl(packet->packet_type)) {

    ERROR: Improper whitespace around control block
    #1075 FILE: lib/packets.c:461:
            switch(nsh->next_proto) {

    WARNING: Line has non-spaces leading whitespace
    #1127 FILE: ofproto/ofproto-dpif-sflow.c:1202:
            case OVS_ACTION_ATTR_ENCAP_NSH:

    WARNING: Line has non-spaces leading whitespace
    #1128 FILE: ofproto/ofproto-dpif-sflow.c:1203:
            case OVS_ACTION_ATTR_DECAP_NSH:

    WARNING: Line length is >79-characters long
    #1275 FILE: ofproto/ofproto-dpif-xlate.c:5811:
        memset(&flow->dl_dst, 0, sizeof(struct flow) - offsetof(struct flow, dl_dst));

I also get the following Clang warnings:

    ../lib/odp-util.c:1843:21: error: cast from 'uint8_t *' (aka 'unsigned char *') to 'struct nsh_md1_ctx *' increases required alignment from 1 to 4 [-Werror,-Wcast-align]
    ../lib/odp-util.c:6813:35: error: cast from 'uint8_t *' (aka 'unsigned char *') to 'struct nsh_md1_ctx *' increases required alignment from 1 to 4 [-Werror,-Wcast-align]
    ../ofproto/ofproto-dpif-xlate.c:5749:43: error: cast from 'const char *' to 'struct ofpact_ed_prop *' increases required alignment from 1 to 2 [-Werror,-Wcast-align]

Thanks,

Ben.
diff mbox

Patch

diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h
index 2381ed2..277c729 100644
--- a/datapath/linux/compat/include/linux/openvswitch.h
+++ b/datapath/linux/compat/include/linux/openvswitch.h
@@ -798,6 +798,25 @@  struct ovs_action_push_eth {
 	struct ovs_key_ethernet addresses;
 };
 
+#define OVS_ENCAP_NSH_MAX_MD_LEN 256
+/*
+ * struct ovs_action_encap_nsh - %OVS_ACTION_ATTR_ENCAP_NSH
+ * @flags: NSH header flags.
+ * @mdtype: NSH metadata type.
+ * @mdlen: Length of NSH metadata in bytes.
+ * @np: NSH next_protocol: Inner packet type.
+ * @path_hdr: NSH service path id and service index.
+ * @metadata: NSH metadata for MD type 1 or 2
+ */
+struct ovs_action_encap_nsh {
+    uint8_t flags;
+    uint8_t mdtype;
+    uint8_t mdlen;
+    uint8_t np;
+    __be32 path_hdr;
+    uint8_t metadata[OVS_ENCAP_NSH_MAX_MD_LEN];
+};
+
 /**
  * enum ovs_nat_attr - Attributes for %OVS_CT_ATTR_NAT.
  *
@@ -873,6 +892,8 @@  enum ovs_nat_attr {
  * @OVS_ACTION_ATTR_PUSH_ETH: Push a new outermost Ethernet header onto the
  * packet.
  * @OVS_ACTION_ATTR_POP_ETH: Pop the outermost Ethernet header off the packet.
+ * @OVS_ACTION_ATTR_ENCAP_NSH: encap NSH action to push NSH header.
+ * @OVS_ACTION_ATTR_DECAP_NSH: decap NSH action to remove NSH header.
  *
  * Only a single header can be set with a single %OVS_ACTION_ATTR_SET.  Not all
  * fields within a header are modifiable, e.g. the IPv4 protocol and fragment
@@ -908,6 +929,8 @@  enum ovs_action_attr {
 	OVS_ACTION_ATTR_TRUNC,        /* u32 struct ovs_action_trunc. */
 	OVS_ACTION_ATTR_PUSH_ETH,     /* struct ovs_action_push_eth. */
 	OVS_ACTION_ATTR_POP_ETH,      /* No argument. */
+	OVS_ACTION_ATTR_ENCAP_NSH,    /* struct ovs_action_encap_nsh. */
+	OVS_ACTION_ATTR_DECAP_NSH,    /* No argument. */
 
 #ifndef __KERNEL__
 	OVS_ACTION_ATTR_TUNNEL_PUSH,   /* struct ovs_action_push_tnl*/
diff --git a/include/openvswitch/nsh.h b/include/openvswitch/nsh.h
index 1593da2..ad89935 100644
--- a/include/openvswitch/nsh.h
+++ b/include/openvswitch/nsh.h
@@ -95,6 +95,8 @@  struct nsh_hdr {
 #define NSH_P_IPV4        0x01
 #define NSH_P_IPV6        0x02
 #define NSH_P_ETHERNET    0x03
+#define NSH_P_NSH         0x04
+#define NSH_P_MPLS        0x05
 
 /* MD Type Registry. */
 #define NSH_M_TYPE1     0x01
@@ -102,8 +104,14 @@  struct nsh_hdr {
 #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
+/* NSH Metadata Length. */
+#define NSH_M_TYPE1_MDLEN 16
+
+/* NSH Base Header Length */
+#define NSH_BASE_HDR_LEN  8
+
+/* NSH MD Type 1 header Length. */
+#define NSH_M_TYPE1_LEN   24
 
 static inline uint16_t
 nsh_hdr_len(const struct nsh_hdr *nsh)
diff --git a/include/openvswitch/ofp-ed-props.h b/include/openvswitch/ofp-ed-props.h
index cf2fa62..306c6fe 100644
--- a/include/openvswitch/ofp-ed-props.h
+++ b/include/openvswitch/ofp-ed-props.h
@@ -27,9 +27,10 @@  extern "C" {
 
 enum ofp_ed_prop_class {
     OFPPPC_BASIC = 0,            /* ONF Basic class. */
-    OFPPPC_MPLS  = 1,            /* MPLS property  class. */
-    OFPPPC_GRE   = 2,            /* GRE property  class. */
-    OFPPPC_GTP   = 3,            /* GTP property  class. */
+    OFPPPC_MPLS  = 1,            /* MPLS property class. */
+    OFPPPC_GRE   = 2,            /* GRE property class. */
+    OFPPPC_GTP   = 3,            /* GTP property class. */
+    OFPPPC_NSH   = 4,            /* NSH property class */
 
     /* Experimenter property class.
      *
@@ -39,6 +40,12 @@  enum ofp_ed_prop_class {
     OFPPPC_EXPERIMENTER=0xffff
 };
 
+enum ofp_ed_nsh_prop_type {
+    OFPPPT_PROP_NSH_NONE = 0,    /* unused */
+    OFPPPT_PROP_NSH_MDTYPE = 1,  /* property MDTYPE in NSH */
+    OFPPPT_PROP_NSH_TLV = 2,     /* property TLV in NSH */
+};
+
 /*
  * External representation of encap/decap properties.
  * These must be padded to a multiple of 8 bytes.
@@ -49,6 +56,22 @@  struct ofp_ed_prop_header {
     uint8_t len;
 };
 
+struct ofp_ed_prop_nsh_md_type {
+    struct ofp_ed_prop_header header;
+    uint8_t md_type;         /* NSH MD type .*/
+    uint8_t pad[3];          /* Padding to 8 bytes. */
+};
+
+struct ofp_ed_prop_nsh_tlv {
+    struct ofp_ed_prop_header header;
+    ovs_be16 tlv_class;      /* Metadata class. */
+    uint8_t tlv_type;        /* Metadata type including C bit. */
+    uint8_t tlv_len;         /* Metadata value length (0-127). */
+
+    /* tlv_len octets of metadata value, padded to a multiple of 8 bytes. */
+    uint8_t data[0];
+};
+
 /*
  * Internal representation of encap/decap properties
  */
@@ -58,6 +81,21 @@  struct ofpact_ed_prop {
     uint8_t len;
 };
 
+struct ofpact_ed_prop_nsh_md_type {
+    struct ofpact_ed_prop header;
+    uint8_t md_type;         /* NSH MD type .*/
+    uint8_t pad[3];          /* Padding to 8 bytes. */
+};
+
+struct ofpact_ed_prop_nsh_tlv {
+    struct ofpact_ed_prop header;
+    ovs_be16 tlv_class;      /* Metadata class. */
+    uint8_t tlv_type;        /* Metadata type including C bit. */
+    uint8_t tlv_len;         /* Metadata value length (0-127). */
+
+    /* tlv_len octets of metadata value, padded to a multiple of 8 bytes. */
+    uint8_t data[0];
+};
 enum ofperr decode_ed_prop(const struct ofp_ed_prop_header **ofp_prop,
                            struct ofpbuf *out, size_t *remaining);
 enum ofperr encode_ed_prop(const struct ofpact_ed_prop **prop,
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index ce51665..4ca5518 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -5408,6 +5408,8 @@  dp_execute_cb(void *aux_, struct dp_packet_batch *packets_,
     case OVS_ACTION_ATTR_PUSH_ETH:
     case OVS_ACTION_ATTR_POP_ETH:
     case OVS_ACTION_ATTR_CLONE:
+    case OVS_ACTION_ATTR_ENCAP_NSH:
+    case OVS_ACTION_ATTR_DECAP_NSH:
     case __OVS_ACTION_ATTR_MAX:
         OVS_NOT_REACHED();
     }
diff --git a/lib/dpif.c b/lib/dpif.c
index 1af53f5..e71f6a3 100644
--- a/lib/dpif.c
+++ b/lib/dpif.c
@@ -1255,6 +1255,8 @@  dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_,
     case OVS_ACTION_ATTR_PUSH_ETH:
     case OVS_ACTION_ATTR_POP_ETH:
     case OVS_ACTION_ATTR_CLONE:
+    case OVS_ACTION_ATTR_ENCAP_NSH:
+    case OVS_ACTION_ATTR_DECAP_NSH:
     case OVS_ACTION_ATTR_UNSPEC:
     case __OVS_ACTION_ATTR_MAX:
         OVS_NOT_REACHED();
diff --git a/lib/match.c b/lib/match.c
index 67fa958..a1e4c5c 100644
--- a/lib/match.c
+++ b/lib/match.c
@@ -1158,9 +1158,9 @@  format_be32_masked_hex(struct ds *s, const char *name,
     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));
+            ds_put_format(s, "0x%"PRIx32, ntohl(value));
         } else {
-            ds_put_format(s, "0x%08"PRIx32"/0x%08"PRIx32,
+            ds_put_format(s, "0x%"PRIx32"/0x%"PRIx32,
                           ntohl(value), ntohl(mask));
         }
         ds_put_char(s, ',');
@@ -1260,18 +1260,20 @@  format_nsh_masked(struct ds *s, const struct flow *f, const struct flow *m)
     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);
+        format_be32_masked_hex(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);
+    if (m->nsh.mdtype == UINT8_MAX && f->nsh.mdtype == NSH_M_TYPE1) {
+        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
diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index f138322..a36ef65 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -296,10 +296,9 @@  odp_set_nsh(struct dp_packet *packet, const struct ovs_key_nsh *key,
                 memcpy(&nsh->md1, &key->c1, sizeof(struct nsh_md1_ctx));
                 break;
             case NSH_M_TYPE2:
-                /* TODO */
-                break;
             default:
-                OVS_NOT_REACHED();
+                /* No support for setting any other metadata format yet. */
+                break;
         }
     } else {
         flags = (ntohs(nsh->ver_flags_len) & NSH_FLAGS_MASK) >> NSH_FLAGS_SHIFT;
@@ -312,20 +311,19 @@  odp_set_nsh(struct dp_packet *packet, const struct ovs_key_nsh *key,
                            path_hdr);
         switch (nsh->md_type) {
             case NSH_M_TYPE1:
-                len = sizeof(struct nsh_md1_ctx) >> 1;
                 /* Avoid the 16/32 bit alignment hassle. */
+                len = sizeof(struct nsh_md1_ctx) >> 1;
                 p = (uint16_t *) &nsh->md1.c1;
                 k = (uint16_t *) &key->c1;
                 m = (uint16_t *) &mask->c1;
-                for (i=0; i<len; i++, p++, k++, m++) {
+                for (i = 0; i < len; i++, p++, k++, m++) {
                     *p = *k | (*p & ~*m);
                 }
                 break;
             case NSH_M_TYPE2:
-                /* TODO */
-                break;
             default:
-                OVS_NOT_REACHED();
+                /* No support for setting any other metadata format yet. */
+                break;
         }
     }
 }
@@ -662,6 +660,8 @@  requires_datapath_assistance(const struct nlattr *a)
     case OVS_ACTION_ATTR_PUSH_ETH:
     case OVS_ACTION_ATTR_POP_ETH:
     case OVS_ACTION_ATTR_CLONE:
+    case OVS_ACTION_ATTR_ENCAP_NSH:
+    case OVS_ACTION_ATTR_DECAP_NSH:
         return false;
 
     case OVS_ACTION_ATTR_UNSPEC:
@@ -826,6 +826,26 @@  odp_execute_actions(void *dp, struct dp_packet_batch *batch, bool steal,
             }
             break;
 
+        case OVS_ACTION_ATTR_ENCAP_NSH: {
+            const struct ovs_action_encap_nsh *enc_nsh = nl_attr_get(a);
+            DP_PACKET_BATCH_FOR_EACH (packet, batch) {
+                encap_nsh(packet, enc_nsh);
+            }
+            break;
+        }
+        case OVS_ACTION_ATTR_DECAP_NSH: {
+            size_t i, num = batch->count;
+
+            DP_PACKET_BATCH_REFILL_FOR_EACH (i, num, packet, batch) {
+                if (decap_nsh(packet)) {
+                    dp_packet_batch_refill(batch, packet, i);
+                } else {
+                    dp_packet_delete(packet);
+                }
+            }
+            break;
+        }
+
         case OVS_ACTION_ATTR_OUTPUT:
         case OVS_ACTION_ATTR_TUNNEL_PUSH:
         case OVS_ACTION_ATTR_TUNNEL_POP:
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 369d0cd..b318587 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -129,6 +129,8 @@  odp_action_len(uint16_t type)
     case OVS_ACTION_ATTR_PUSH_ETH: return sizeof(struct ovs_action_push_eth);
     case OVS_ACTION_ATTR_POP_ETH: return 0;
     case OVS_ACTION_ATTR_CLONE: return ATTR_LEN_VARIABLE;
+    case OVS_ACTION_ATTR_ENCAP_NSH: return ATTR_LEN_VARIABLE;
+    case OVS_ACTION_ATTR_DECAP_NSH: return 0;
 
     case OVS_ACTION_ATTR_UNSPEC:
     case __OVS_ACTION_ATTR_MAX:
@@ -254,21 +256,20 @@  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, ",spi=0x%x", (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));
+            ds_put_format(ds, ",c1=0x%x", ntohl(key->c1));
+            ds_put_format(ds, ",c2=0x%x", ntohl(key->c2));
+            ds_put_format(ds, ",c3=0x%x", ntohl(key->c3));
+            ds_put_format(ds, ",c4=0x%x", ntohl(key->c4));
             break;
         case NSH_M_TYPE2:
-            /* TODO */
-            break;
         default:
-            OVS_NOT_REACHED();
+            /* No support for other matching other metadata formats yet. */
+            break;
     }
 }
 
@@ -300,9 +301,9 @@  format_be32_masked(struct ds *s, bool *first, const char *name,
         }
         ds_put_format(s, "%s=", name);
         if (mask == OVS_BE32_MAX) {
-            ds_put_format(s, "0x%08"PRIx32, ntohl(value));
+            ds_put_format(s, "0x%"PRIx32, ntohl(value));
         } else {
-            ds_put_format(s, "0x%08"PRIx32"/0x%08"PRIx32,
+            ds_put_format(s, "0x%"PRIx32"/0x%"PRIx32,
                           ntohl(value), ntohl(mask));
         }
         *first = false;
@@ -337,6 +338,40 @@  format_nsh_key_mask(struct ds *ds, const struct ovs_key_nsh *key,
     }
 }
 
+static void
+format_odp_encap_nsh_action(struct ds *ds,
+                            const struct ovs_action_encap_nsh *encap_nsh)
+ {
+    uint32_t path_hdr = ntohl(encap_nsh->path_hdr);
+    uint32_t spi = (path_hdr & NSH_SPI_MASK) >> NSH_SPI_SHIFT;
+    uint8_t si = (path_hdr & NSH_SI_MASK) >> NSH_SI_SHIFT;
+
+    ds_put_cstr(ds, "encap_nsh(");
+    ds_put_format(ds, "flags=%d", encap_nsh->flags);
+    ds_put_format(ds, ",mdtype=%d", encap_nsh->mdtype);
+    ds_put_format(ds, ",np=%d", encap_nsh->np);
+    ds_put_format(ds, ",spi=0x%x", spi);
+    ds_put_format(ds, ",si=%d", si);
+    switch (encap_nsh->mdtype) {
+    case NSH_M_TYPE1: {
+        struct nsh_md1_ctx *md1_ctx =
+                ALIGNED_CAST(struct nsh_md1_ctx *, encap_nsh->metadata);
+        ds_put_format(ds, ",c1=0x%x", ntohl(md1_ctx->c1));
+        ds_put_format(ds, ",c2=0x%x", ntohl(md1_ctx->c2));
+        ds_put_format(ds, ",c3=0x%x", ntohl(md1_ctx->c3));
+        ds_put_format(ds, ",c4=0x%x", ntohl(md1_ctx->c4));
+        break;
+    }
+    case NSH_M_TYPE2:
+        ds_put_cstr(ds, ",md2=");
+        ds_put_hex(ds, encap_nsh->metadata, encap_nsh->mdlen);
+        break;
+    default:
+        OVS_NOT_REACHED();
+    }
+    ds_put_format(ds, ")");
+}
+
 static const char *
 slow_path_reason_to_string(uint32_t reason)
 {
@@ -632,7 +667,7 @@  format_odp_tnl_push_header(struct ds *ds, struct ovs_action_push_tnl *data)
                       gnh->oam ? "oam," : "",
                       gnh->critical ? "crit," : "",
                       ntohl(get_16aligned_be32(&gnh->vni)) >> 8);
- 
+
         if (gnh->opt_len) {
             ds_put_cstr(ds, ",options(");
             format_geneve_opts(gnh->options, NULL, gnh->opt_len * 4,
@@ -1019,6 +1054,12 @@  format_odp_action(struct ds *ds, const struct nlattr *a,
     case OVS_ACTION_ATTR_CLONE:
         format_odp_clone_action(ds, a, portno_names);
         break;
+    case OVS_ACTION_ATTR_ENCAP_NSH:
+        format_odp_encap_nsh_action(ds, nl_attr_get(a));
+        break;
+    case OVS_ACTION_ATTR_DECAP_NSH:
+        ds_put_cstr(ds, "decap_nsh()");
+        break;
     case OVS_ACTION_ATTR_UNSPEC:
     case __OVS_ACTION_ATTR_MAX:
     default:
@@ -1737,6 +1778,113 @@  find_end:
 }
 
 static int
+parse_odp_encap_nsh_action(const char *s, struct ofpbuf *actions)
+{
+    int n = 0;
+    int ret = 0;
+    struct ovs_action_encap_nsh encap_nsh;
+    uint32_t spi;
+    uint8_t si;
+    uint32_t cd;
+
+    if (!ovs_scan_len(s, &n, "encap_nsh(")) {
+        ret = -EINVAL;
+        goto out;
+    }
+
+    /* The default is NSH_M_TYPE1 */
+    encap_nsh.flags = 0;
+    encap_nsh.mdtype = NSH_M_TYPE1;
+    encap_nsh.mdlen = NSH_M_TYPE1_MDLEN;
+    encap_nsh.path_hdr = htonl(255);
+    memset(encap_nsh.metadata, 0, NSH_M_TYPE1_MDLEN);
+
+    for (;;) {
+        n += strspn(s + n, delimiters);
+        if (s[n] == ')') {
+            break;
+        }
+
+        if (ovs_scan_len(s, &n, "flags=%"SCNi8, &encap_nsh.flags)) {
+            continue;
+        }
+        if (ovs_scan_len(s, &n, "mdtype=%"SCNi8, &encap_nsh.mdtype)) {
+            switch (encap_nsh.mdtype) {
+            case NSH_M_TYPE1:
+                /* This is the default format. */;
+                break;
+            case NSH_M_TYPE2:
+                /* Length will be updated later. */
+                encap_nsh.mdlen = 0;
+                break;
+            default:
+                ret = -EINVAL;
+                goto out;
+            }
+            continue;
+        }
+        if (ovs_scan_len(s, &n, "np=%"SCNi8, &encap_nsh.np)) {
+            continue;
+        }
+        if (ovs_scan_len(s, &n, "spi=0x%"SCNx32, &spi)) {
+            encap_nsh.path_hdr =
+                    htonl(((spi << NSH_SPI_SHIFT) & NSH_SPI_MASK) |
+                            (ntohl(encap_nsh.path_hdr) & ~NSH_SPI_MASK));
+            continue;
+        }
+        if (ovs_scan_len(s, &n, "si=%"SCNi8, &si)) {
+            encap_nsh.path_hdr =
+                    htonl((si << NSH_SI_SHIFT) |
+                            (ntohl(encap_nsh.path_hdr) & ~NSH_SI_MASK));
+            continue;
+        }
+        if (encap_nsh.mdtype == NSH_M_TYPE1) {
+            struct nsh_md1_ctx *md1 =
+                    (struct nsh_md1_ctx *) encap_nsh.metadata;
+            if (ovs_scan_len(s, &n, "c1=0x%"SCNx32, &cd)) {
+                md1->c1 = htonl(cd);
+                continue;
+            }
+            if (ovs_scan_len(s, &n, "c2=0x%"SCNx32, &cd)) {
+                md1->c2 = htonl(cd);
+                continue;
+            }
+            if (ovs_scan_len(s, &n, "c3=0x%"SCNx32, &cd)) {
+                md1->c3 = htonl(cd);
+                continue;
+            }
+            if (ovs_scan_len(s, &n, "c4=0x%"SCNx32, &cd)) {
+                md1->c4 = htonl(cd);
+                continue;
+            }
+        }
+        else if (encap_nsh.mdtype == NSH_M_TYPE2) {
+            struct ofpbuf b;
+            char buf[512];
+            size_t mdlen;
+            if (ovs_scan_len(s, &n, "md2=0x%511[0-9a-fA-F]", buf)) {
+                ofpbuf_use_stub(&b, encap_nsh.metadata,
+                                OVS_ENCAP_NSH_MAX_MD_LEN);
+                ofpbuf_put_hex(&b, buf, &mdlen);
+                encap_nsh.mdlen = mdlen;
+                ofpbuf_uninit(&b);
+            }
+            continue;
+        }
+    }
+out:
+    if (ret < 0) {
+        return ret;
+    } else {
+        size_t size = offsetof(struct ovs_action_encap_nsh, metadata)
+                + ROUND_UP(encap_nsh.mdlen, 4);
+        nl_msg_put_unspec(actions, OVS_ACTION_ATTR_ENCAP_NSH,
+                          &encap_nsh, size);
+        return n;
+    }
+}
+
+static int
 parse_action_list(const char *s, const struct simap *port_names,
                   struct ofpbuf *actions)
 {
@@ -1938,6 +2086,24 @@  parse_odp_action(const char *s, const struct simap *port_names,
     }
 
     {
+        if (!strncmp(s, "encap_nsh(", 10)) {
+            int retval = parse_odp_encap_nsh_action(s, actions);
+            if (retval < 0) {
+                return retval;
+            }
+            return retval + 1;
+        }
+    }
+
+    {
+        int n;
+        if (ovs_scan(s, "decap_nsh()%n", &n)) {
+            nl_msg_put_flag(actions, OVS_ACTION_ATTR_DECAP_NSH);
+            return n;
+        }
+    }
+
+    {
         uint32_t port;
         int n;
 
@@ -2451,6 +2617,7 @@  format_eth(struct ds *ds, const char *name, const struct eth_addr key,
     }
 }
 
+
 static void
 format_be64(struct ds *ds, const char *name, ovs_be64 key,
             const ovs_be64 *mask, bool verbose)
@@ -6466,7 +6633,8 @@  get_nsh_key(const struct flow *flow, struct ovs_key_nsh *nsh, bool is_mask)
             nsh->c4 = flow->nsh.c4;
             break;
         case NSH_M_TYPE2:
-            /* TODO: MD type 2 */
+        default:
+            /* No match support for other MD formats yet. */
             break;
         }
     }
@@ -6488,11 +6656,12 @@  put_nsh_key(const struct ovs_key_nsh *nsh, struct flow *flow, bool is_mask OVS_U
             flow->nsh.c4 = nsh->c4;
             break;
         case NSH_M_TYPE2:
-            /* TODO: MD type 2 */
+        default:
             flow->nsh.c1 = 0;
             flow->nsh.c2 = 0;
             flow->nsh.c3 = 0;
             flow->nsh.c4 = 0;
+            /* No match support for other MD formats yet. */
             break;
     }
 }
@@ -6624,11 +6793,57 @@  commit_set_pkt_mark_action(const struct flow *flow, struct flow *base_flow,
 }
 
 static void
+odp_put_decap_nsh_action(struct ofpbuf *odp_actions)
+{
+    nl_msg_put_flag(odp_actions, OVS_ACTION_ATTR_DECAP_NSH);
+}
+
+static void
+odp_put_encap_nsh_action(struct ofpbuf *odp_actions,
+                         const struct flow *flow,
+                         struct ofpbuf *encap_data)
+{
+    struct ovs_action_encap_nsh encap_nsh;
+
+    encap_nsh.flags = flow->nsh.flags;
+    encap_nsh.mdtype = flow->nsh.mdtype;
+    encap_nsh.np = flow->nsh.np;
+    encap_nsh.path_hdr = htonl((ntohl(flow->nsh.spi) << NSH_SPI_SHIFT) | flow->nsh.si);
+
+    switch (encap_nsh.mdtype) {
+    case NSH_M_TYPE1: {
+        struct nsh_md1_ctx *md1 = (struct nsh_md1_ctx *) encap_nsh.metadata;
+        encap_nsh.mdlen = NSH_M_TYPE1_MDLEN;
+        md1->c1 = flow->nsh.c1;
+        md1->c2 = flow->nsh.c2;
+        md1->c3 = flow->nsh.c3;
+        md1->c4 = flow->nsh.c4;
+        break;
+    }
+    case NSH_M_TYPE2:
+        if (encap_data) {
+            ovs_assert(encap_data->size < OVS_ENCAP_NSH_MAX_MD_LEN);
+            encap_nsh.mdlen = encap_data->size;
+            memcpy(encap_nsh.metadata, encap_data->data, encap_data->size);
+        } else {
+            encap_nsh.mdlen = 0;
+        }
+        break;
+    default:
+        encap_nsh.mdlen = 0;
+        break;
+    }
+    nl_msg_put_unspec(odp_actions, OVS_ACTION_ATTR_ENCAP_NSH,
+                      &encap_nsh, sizeof(encap_nsh));
+}
+
+static void
 commit_packet_type_change(const struct flow *flow,
                           struct flow *base_flow,
                           struct ofpbuf *odp_actions,
                           struct flow_wildcards *wc,
-                          bool pending_encap)
+                          bool pending_encap,
+                          struct ofpbuf *encap_data)
 {
     if (flow->packet_type == base_flow->packet_type) {
         return;
@@ -6645,22 +6860,40 @@  commit_packet_type_change(const struct flow *flow,
             base_flow->dl_dst = flow->dl_dst;
             break;
         }
+        case PT_NSH:
+            /* encap_nsh */
+            odp_put_encap_nsh_action(odp_actions, flow, encap_data);
+            base_flow->packet_type = flow->packet_type;
+            /* Update all packet headers in base_flow. */
+            memcpy(&base_flow->dl_dst, &flow->dl_dst,
+                   sizeof(*flow) - offsetof(struct flow, dl_dst));
+            break;
         default:
-            /* Only the above protocols are supported for encap. The check
-             * is done at action decoding. */
+            /* Only the above protocols are supported for encap.
+             * The check is done at action translation. */
             OVS_NOT_REACHED();
         }
     } else {
+        /* This is an explicit or implicit decap case. */
         if (pt_ns(flow->packet_type) == OFPHTN_ETHERTYPE &&
             base_flow->packet_type == htonl(PT_ETH)) {
-            /* pop_eth */
+            /* Generate pop_eth and continue without recirculation. */
             odp_put_pop_eth_action(odp_actions);
             base_flow->packet_type = flow->packet_type;
             base_flow->dl_src = eth_addr_zero;
             base_flow->dl_dst = eth_addr_zero;
         } else {
-            /* All other cases are handled through recirculation. */
-            OVS_NOT_REACHED();
+            /* All other decap cases require recirculation.
+             * No need to update the base flow here. */
+            switch (ntohl(base_flow->packet_type)) {
+            case PT_NSH:
+                /* decap_nsh. */
+                odp_put_decap_nsh_action(odp_actions);
+                break;
+            default:
+                /* Checks are done during translation. */
+                OVS_NOT_REACHED();
+            }
         }
     }
 
@@ -6679,12 +6912,14 @@  commit_packet_type_change(const struct flow *flow,
 enum slow_path_reason
 commit_odp_actions(const struct flow *flow, struct flow *base,
                    struct ofpbuf *odp_actions, struct flow_wildcards *wc,
-                   bool use_masked, bool pending_encap)
+                   bool use_masked, bool pending_encap,
+                   struct ofpbuf *encap_data)
 {
     enum slow_path_reason slow1, slow2;
     bool mpls_done = false;
 
-    commit_packet_type_change(flow, base, odp_actions, wc, pending_encap);
+    commit_packet_type_change(flow, base, odp_actions, wc,
+                              pending_encap, encap_data);
     commit_set_ether_action(flow, base, odp_actions, wc, use_masked);
     /* Make packet a non-MPLS packet before committing L3/4 actions,
      * which would otherwise do nothing. */
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 111cd9c..27c2ab4 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -277,7 +277,8 @@  enum slow_path_reason commit_odp_actions(const struct flow *,
                                          struct ofpbuf *odp_actions,
                                          struct flow_wildcards *wc,
                                          bool use_masked,
-                                         bool pending_encap);
+                                         bool pending_encap,
+                                         struct ofpbuf *encap_data);
 
 /* ofproto-dpif interface.
  *
diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
index 16da78c..6a17433 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -4055,6 +4055,7 @@  decode_NXAST_RAW_ENCAP(const struct nx_action_encap *nae,
     encap->ofpact.raw = NXAST_RAW_ENCAP;
     switch (ntohl(nae->new_pkt_type)) {
     case PT_ETH:
+    case PT_NSH:
         /* Add supported encap header types here. */
         break;
     default:
@@ -4103,6 +4104,8 @@  parse_encap_header(const char *hdr, ovs_be32 *packet_type)
 {
     if (strcmp(hdr, "ethernet") == 0) {
         *packet_type = htonl(PT_ETH);
+    } else if (strcmp(hdr, "nsh") == 0) {
+        *packet_type = htonl(PT_NSH);
     } else {
         return false;
     }
@@ -4178,6 +4181,8 @@  format_encap_pkt_type(const ovs_be32 pkt_type)
     switch (ntohl(pkt_type)) {
     case PT_ETH:
         return "ethernet";
+    case PT_NSH:
+        return "nsh";
     default:
         return "UNKNOWN";
     }
diff --git a/lib/ofp-ed-props.c b/lib/ofp-ed-props.c
index a346138..c3c4055 100644
--- a/lib/ofp-ed-props.c
+++ b/lib/ofp-ed-props.c
@@ -30,6 +30,7 @@  decode_ed_prop(const struct ofp_ed_prop_header **ofp_prop,
                size_t *remaining)
 {
     uint16_t prop_class = ntohs((*ofp_prop)->prop_class);
+    uint8_t prop_type = (*ofp_prop)->type;
     size_t len = (*ofp_prop)->len;
     size_t pad_len = ROUND_UP(len, 8);
 
@@ -38,6 +39,45 @@  decode_ed_prop(const struct ofp_ed_prop_header **ofp_prop,
     }
 
     switch (prop_class) {
+    case OFPPPC_NSH: {
+        switch (prop_type) {
+        case OFPPPT_PROP_NSH_MDTYPE: {
+            struct ofp_ed_prop_nsh_md_type *opnmt =
+                ALIGNED_CAST(struct ofp_ed_prop_nsh_md_type *, *ofp_prop);
+            if (len > sizeof(*opnmt) || len > *remaining) {
+                return OFPERR_NXBAC_BAD_ED_PROP;
+            }
+            struct ofpact_ed_prop_nsh_md_type *pnmt =
+                    ofpbuf_put_uninit(out, sizeof(*pnmt));
+            pnmt->header.prop_class = prop_class;
+            pnmt->header.type = prop_type;
+            pnmt->header.len = len;
+            pnmt->md_type = opnmt->md_type;
+            break;
+        }
+        case OFPPPT_PROP_NSH_TLV: {
+            struct ofp_ed_prop_nsh_tlv *opnt =
+                ALIGNED_CAST(struct ofp_ed_prop_nsh_tlv *, *ofp_prop);
+            size_t tlv_pad_len = ROUND_UP(opnt->tlv_len, 8);
+            if (len != sizeof(*opnt) + tlv_pad_len || len > *remaining) {
+                return OFPERR_NXBAC_BAD_ED_PROP;
+            }
+            struct ofpact_ed_prop_nsh_tlv *pnt =
+                    ofpbuf_put_uninit(out, sizeof(*pnt));
+            pnt->header.prop_class = prop_class;
+            pnt->header.type = prop_type;
+            pnt->header.len = len;
+            pnt->tlv_class = opnt->tlv_class;
+            pnt->tlv_type = opnt->tlv_type;
+            pnt->tlv_len = opnt->tlv_len;
+            ofpbuf_put(out, opnt->data, tlv_pad_len);
+            break;
+        }
+        default:
+            return OFPERR_NXBAC_UNKNOWN_ED_PROP;
+        }
+        break;
+    }
     default:
         return OFPERR_NXBAC_UNKNOWN_ED_PROP;
     }
@@ -55,6 +95,43 @@  encode_ed_prop(const struct ofpact_ed_prop **prop,
     size_t prop_len;
 
     switch ((*prop)->prop_class) {
+    case OFPPPC_NSH: {
+        switch ((*prop)->type) {
+        case OFPPPT_PROP_NSH_MDTYPE: {
+            struct ofpact_ed_prop_nsh_md_type *pnmt =
+                ALIGNED_CAST(struct ofpact_ed_prop_nsh_md_type *, *prop);
+            struct ofp_ed_prop_nsh_md_type *opnmt =
+                    ofpbuf_put_uninit(out, sizeof(*opnmt));
+            opnmt->header.prop_class = htons((*prop)->prop_class);
+            opnmt->header.type = (*prop)->type;
+            opnmt->header.len =
+                    offsetof(struct ofp_ed_prop_nsh_md_type, pad);
+            opnmt->md_type = pnmt->md_type;
+            prop_len = sizeof(*pnmt);
+            break;
+        }
+        case OFPPPT_PROP_NSH_TLV: {
+            struct ofpact_ed_prop_nsh_tlv *pnt =
+                ALIGNED_CAST(struct ofpact_ed_prop_nsh_tlv *, *prop);
+            struct ofp_ed_prop_nsh_tlv *opnt;
+            size_t tlv_pad_len = ROUND_UP(pnt->tlv_len, 8);
+            size_t len = sizeof(*opnt) + tlv_pad_len;
+            opnt = ofpbuf_put_uninit(out, len);
+            opnt->header.prop_class = htons((*prop)->prop_class);
+            opnt->header.type = (*prop)->type;
+            opnt->header.len = len;
+            opnt->tlv_class = pnt->tlv_class;
+            opnt->tlv_type = pnt->tlv_type;
+            opnt->tlv_len = pnt->tlv_len;
+            memcpy(opnt->data, pnt->data, tlv_pad_len);
+            prop_len = sizeof(*pnt) + tlv_pad_len;
+            break;
+        }
+        default:
+            return OFPERR_OFPBAC_BAD_ARGUMENT;
+        }
+        break;
+    }
     default:
         return OFPERR_OFPBAC_BAD_ARGUMENT;
     }
@@ -78,6 +155,8 @@  parse_ed_prop_class(const char *str OVS_UNUSED,
         *prop_class = OFPPPC_GRE;
     } else if (!strcmp(str,"gtp")) {
         *prop_class = OFPPPC_GTP;
+    } else if (!strcmp(str,"nsh")) {
+        *prop_class = OFPPPC_NSH;
     } else {
         return false;
     }
@@ -90,6 +169,16 @@  parse_ed_prop_type(uint16_t prop_class,
                    uint8_t *type OVS_UNUSED)
 {
     switch (prop_class) {
+    case OFPPPC_NSH:
+        if (!strcmp(str, "md_type")) {
+            *type = OFPPPT_PROP_NSH_MDTYPE;
+            return true;
+        } else if (!strcmp(str, "tlv")) {
+            *type = OFPPPT_PROP_NSH_TLV;
+            return true;
+        } else {
+            return false;
+        }
     default:
         return false;
     }
@@ -106,12 +195,68 @@  char *
 parse_ed_prop_value(uint16_t prop_class, uint8_t prop_type OVS_UNUSED,
                     const char *value, struct ofpbuf *out OVS_UNUSED)
 {
+    char *error = NULL;
 
     if (value == NULL || *value == '\0') {
         return xstrdup("Value missing for encap property");
     }
 
     switch (prop_class) {
+    case OFPPPC_NSH:
+        switch (prop_type) {
+        case OFPPPT_PROP_NSH_MDTYPE: {
+            /* Format: "<md_type>:uint8_t". */
+            uint8_t md_type;
+            error = str_to_u8(value, "md_type", &md_type);
+            if (error != NULL) {
+                return error;
+            }
+            if (md_type < 1 || md_type > 2) {
+                return xstrdup("invalid md_type");
+            }
+            struct ofpact_ed_prop_nsh_md_type *pnmt =
+                    ofpbuf_put_uninit(out, sizeof(*pnmt));
+            pnmt->header.prop_class = prop_class;
+            pnmt->header.type = prop_type;
+            pnmt->header.len =
+                    offsetof(struct ofp_ed_prop_nsh_md_type, pad);
+            pnmt->md_type = md_type;
+            break;
+        }
+        case OFPPPT_PROP_NSH_TLV: {
+            /* Format: "<tlv_class>:ovs_be16,<tlv_type>:uint8_t,<tlv_val>:hex_string" */
+            struct ofpact_ed_prop_nsh_tlv *pnt;
+            uint16_t tlv_class;
+            uint8_t tlv_type;
+            char buf[256];
+            size_t tlv_value_len, padding;
+            size_t start_ofs = out->size;
+
+            if (!ovs_scan(value, "0x%"SCNx16",%"SCNu8",0x%251[0-9a-fA-F]",
+                          &tlv_class, &tlv_type, buf)) {
+                return xasprintf("Invalid NSH TLV header: %s", value);
+            }
+            ofpbuf_put_uninit(out, sizeof(*pnt));
+            ofpbuf_put_hex(out, buf, &tlv_value_len);
+            pnt = ALIGNED_CAST(struct ofpact_ed_prop_nsh_tlv *,
+                               ((char *)out->data + start_ofs));
+            padding = ROUND_UP(tlv_value_len, 8) - tlv_value_len;
+            pnt->header.prop_class = prop_class;
+            pnt->header.type = prop_type;
+            pnt->header.len = sizeof(*pnt) + tlv_value_len + padding;
+            pnt->tlv_class = htons(tlv_class);
+            pnt->tlv_type = tlv_type;
+            pnt->tlv_len = tlv_value_len;
+            if (padding > 0) {
+                ofpbuf_put_zeros(out, padding);
+            }
+            break;
+        }
+        default:
+            /* Unsupported property types rejected before. */
+            OVS_NOT_REACHED();
+        }
+        break;
     default:
         /* Unsupported property classes rejected before. */
         OVS_NOT_REACHED();
@@ -132,6 +277,8 @@  format_ed_prop_class(const struct ofpact_ed_prop *prop)
         return "gre";
     case OFPPPC_GTP:
         return "gtp";
+    case OFPPPC_NSH:
+        return "nsh";
     default:
         OVS_NOT_REACHED();
     }
@@ -141,6 +288,16 @@  char *
 format_ed_prop_type(const struct ofpact_ed_prop *prop)
 {
     switch (prop->prop_class) {
+    case OFPPPC_NSH:
+        switch (prop->type) {
+        case OFPPPT_PROP_NSH_MDTYPE:
+            return "md_type";
+        case OFPPPT_PROP_NSH_TLV:
+            return "tlv";
+        default:
+            OVS_NOT_REACHED();
+        }
+        break;
     default:
         OVS_NOT_REACHED();
     }
@@ -151,6 +308,28 @@  format_ed_prop(struct ds *s OVS_UNUSED,
                      const struct ofpact_ed_prop *prop)
 {
     switch (prop->prop_class) {
+    case OFPPPC_NSH:
+        switch (prop->type) {
+        case OFPPPT_PROP_NSH_MDTYPE: {
+            struct ofpact_ed_prop_nsh_md_type *pnmt =
+                ALIGNED_CAST(struct ofpact_ed_prop_nsh_md_type *, prop);
+            ds_put_format(s, "%s=%d", format_ed_prop_type(prop),
+                          pnmt->md_type);
+            return;
+        }
+        case OFPPPT_PROP_NSH_TLV: {
+            struct ofpact_ed_prop_nsh_tlv *pnt =
+                ALIGNED_CAST(struct ofpact_ed_prop_nsh_tlv *, prop);
+            ds_put_format(s, "%s(0x%04x,%d,",
+                          format_ed_prop_type(prop),
+                          ntohs(pnt->tlv_class), pnt->tlv_type);
+            ds_put_hex(s, pnt->data, pnt->tlv_len);
+            ds_put_cstr(s,")");
+            return;
+        }
+        default:
+            OVS_NOT_REACHED();
+        }
     default:
         OVS_NOT_REACHED();
     }
diff --git a/lib/packets.c b/lib/packets.c
index 7a9071c..48fa79a 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -402,6 +402,88 @@  pop_mpls(struct dp_packet *packet, ovs_be16 ethtype)
     }
 }
 
+void
+encap_nsh(struct dp_packet *packet, const struct ovs_action_encap_nsh *encap)
+{
+    struct nsh_hdr *nsh;
+    size_t length = NSH_BASE_HDR_LEN + encap->mdlen;
+    uint8_t next_proto;
+
+    switch(ntohl(packet->packet_type)) {
+        case PT_ETH:
+            next_proto = NSH_P_ETHERNET;
+            break;
+        case PT_IPV4:
+            next_proto = NSH_P_IPV4;
+            break;
+        case PT_IPV6:
+            next_proto = NSH_P_IPV6;
+            break;
+        case PT_NSH:
+            next_proto = NSH_P_NSH;
+            break;
+        default:
+            OVS_NOT_REACHED();
+    }
+
+    nsh = (struct nsh_hdr *) dp_packet_push_uninit(packet, length);
+    nsh->ver_flags_len = htons(encap->flags << NSH_FLAGS_SHIFT | length >> 2);
+    nsh->next_proto = next_proto;
+    nsh->path_hdr = encap->path_hdr;
+    nsh->md_type = encap->mdtype;
+    switch (nsh->md_type) {
+        case NSH_M_TYPE1:
+            nsh->md1 = *ALIGNED_CAST(struct nsh_md1_ctx *, encap->metadata);
+            break;
+        case NSH_M_TYPE2: {
+            /* The MD2 metadata in encap is already padded to 4 bytes. */
+            size_t len = ROUND_UP(encap->mdlen, 4);
+            memcpy(nsh->md2, encap->metadata, len);
+            break;
+        }
+        default:
+            OVS_NOT_REACHED();
+    }
+
+    packet->packet_type = htonl(PT_NSH);
+    dp_packet_reset_offsets(packet);
+    packet->l3_ofs = 0;
+}
+
+bool
+decap_nsh(struct dp_packet *packet)
+{
+    struct nsh_hdr *nsh = (struct nsh_hdr *) dp_packet_l3(packet);
+    size_t length;
+    uint32_t next_pt;
+
+    if (packet->packet_type == htonl(PT_NSH) && nsh) {
+        switch(nsh->next_proto) {
+            case NSH_P_ETHERNET:
+                next_pt = PT_ETH;
+                break;
+            case NSH_P_IPV4:
+                next_pt = PT_IPV4;
+                break;
+            case NSH_P_IPV6:
+                next_pt = PT_IPV6;
+                break;
+            case NSH_P_NSH:
+                next_pt = PT_NSH;
+                break;
+            default:
+                /* Unknown inner packet type. Drop packet. */
+                return false;
+        }
+
+        length = nsh_hdr_len(nsh);
+        dp_packet_reset_packet(packet, length);
+        packet->packet_type = htonl(next_pt);
+        /* Packet must be recirculated for further processing. */
+    }
+    return true;
+}
+
 /* Converts hex digits in 'hex' to an Ethernet packet in '*packetp'.  The
  * caller must free '*packetp'.  On success, returns NULL.  On failure, returns
  * an error message and stores NULL in '*packetp'.
diff --git a/lib/packets.h b/lib/packets.h
index e8b09e5..466b863 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -415,6 +415,10 @@  void push_eth(struct dp_packet *packet, const struct eth_addr *dst,
               const struct eth_addr *src);
 void pop_eth(struct dp_packet *packet);
 
+void encap_nsh(struct dp_packet *packet,
+               const struct ovs_action_encap_nsh *encap_nsh);
+bool decap_nsh(struct dp_packet *packet);
+
 #define LLC_DSAP_SNAP 0xaa
 #define LLC_SSAP_SNAP 0xaa
 #define LLC_CNTL_SNAP 3
diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
index e4cca65..baa3526 100644
--- a/ofproto/ofproto-dpif-sflow.c
+++ b/ofproto/ofproto-dpif-sflow.c
@@ -1199,6 +1199,8 @@  dpif_sflow_read_actions(const struct flow *flow,
 	    break;
 	case OVS_ACTION_ATTR_SAMPLE:
 	case OVS_ACTION_ATTR_CLONE:
+	case OVS_ACTION_ATTR_ENCAP_NSH:
+	case OVS_ACTION_ATTR_DECAP_NSH:
 	case OVS_ACTION_ATTR_UNSPEC:
 	case __OVS_ACTION_ATTR_MAX:
 	default:
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 2a3cd5a..d4cf36d 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -55,6 +55,7 @@ 
 #include "openvswitch/meta-flow.h"
 #include "openvswitch/list.h"
 #include "openvswitch/ofp-actions.h"
+#include "openvswitch/ofp-ed-props.h"
 #include "openvswitch/vlog.h"
 #include "ovs-lldp.h"
 #include "ovs-router.h"
@@ -234,8 +235,10 @@  struct xlate_ctx {
     bool in_action_set;         /* Currently translating action_set, if true. */
     bool in_packet_out;         /* Currently translating a packet_out msg, if
                                  * true. */
-    bool pending_encap;         /* Waiting to commit a pending encap
-                                 * action, if true. */
+    bool pending_encap;         /* True when waiting to commit a pending
+                                 * encap action. */
+    struct ofpbuf *encap_data;  /* May contain a pointer to an ofpbuf with
+                                 * context for the datapath encap action.*/
 
     uint8_t table_id;           /* OpenFlow table ID where flow was found. */
     ovs_be64 rule_cookie;       /* Cookie of the rule being translated. */
@@ -3467,8 +3470,11 @@  xlate_commit_actions(struct xlate_ctx *ctx)
 
     ctx->xout->slow |= commit_odp_actions(&ctx->xin->flow, &ctx->base_flow,
                                           ctx->odp_actions, ctx->wc,
-                                          use_masked, ctx->pending_encap);
+                                          use_masked, ctx->pending_encap,
+                                          ctx->encap_data);
     ctx->pending_encap = false;
+    ofpbuf_delete(ctx->encap_data);
+    ctx->encap_data = NULL;
 }
 
 static void
@@ -4385,6 +4391,8 @@  xlate_fixup_actions(struct ofpbuf *b, const struct nlattr *actions,
         case OVS_ACTION_ATTR_CT:
         case OVS_ACTION_ATTR_PUSH_ETH:
         case OVS_ACTION_ATTR_POP_ETH:
+        case OVS_ACTION_ATTR_ENCAP_NSH:
+        case OVS_ACTION_ATTR_DECAP_NSH:
         case OVS_ACTION_ATTR_METER:
             ofpbuf_put(b, a, nl_attr_len_pad(a, left));
             break;
@@ -5710,20 +5718,130 @@  rewrite_flow_encap_ethernet(struct xlate_ctx *ctx,
         flow->dl_dst = eth_addr_zero;
         flow->dl_type = ethertype;
     } else {
+        /* Error handling: drop packet. */
         xlate_report_debug(ctx, OFT_ACTION,
-                           "encap(ethernet) unsupported for packet type "
-                           "ethernet");
-        /* TODO: Error handling: drop packet. */
+                           "Dropping packet as encap(ethernet) is not "
+                           "supported for packet type ethernet.");
         ctx->error = 1;
     }
 }
 
+/* For an MD2 NSH header returns a pointer to an ofpbuf with the encoded
+ * MD2 TLVs provided as encap properties to the encap operation. This
+ * will be stored as encap_data in the ctx and copied into the encap_nsh
+ * action at the next commit. */
+static struct ofpbuf *
+rewrite_flow_encap_nsh(struct xlate_ctx *ctx,
+                       const struct ofpact_encap *encap,
+                       struct flow *flow,
+                       struct flow_wildcards *wc)
+{
+    ovs_be32 packet_type = flow->packet_type;
+    const char *ptr = (char *) encap->props;
+    struct ofpbuf *buf = ofpbuf_new(256);
+    uint8_t md_type = NSH_M_TYPE1;
+    uint8_t np = 0;
+    int i;
+
+    /* Scan the optional NSH encap TLV properties, if any. */
+    for (i = 0; i < encap->n_props; i++) {
+        struct ofpact_ed_prop *prop_ptr = (struct ofpact_ed_prop *) ptr;
+        if (prop_ptr->prop_class == OFPPPC_NSH) {
+            switch (prop_ptr->type) {
+                case OFPPPT_PROP_NSH_MDTYPE: {
+                    struct ofpact_ed_prop_nsh_md_type *prop_md_type =
+                            (struct ofpact_ed_prop_nsh_md_type *) prop_ptr;
+                    md_type = prop_md_type->md_type;
+                    break;
+                }
+                case OFPPPT_PROP_NSH_TLV: {
+                    struct ofpact_ed_prop_nsh_tlv *tlv_prop =
+                            (struct ofpact_ed_prop_nsh_tlv *) prop_ptr;
+                    struct nsh_md2_tlv *md2_ctx =
+                            ofpbuf_put_uninit(buf, sizeof(*md2_ctx));
+                    md2_ctx->md_class = tlv_prop->tlv_class;
+                    md2_ctx->type = tlv_prop->tlv_type;
+                    md2_ctx->length = tlv_prop->tlv_len;
+                    size_t len = ROUND_UP(md2_ctx->length, 4);
+                    size_t padding = len - md2_ctx->length;
+                    ofpbuf_put(buf, tlv_prop->data, md2_ctx->length);
+                    ofpbuf_put_zeros(buf, padding);
+                    break;
+                }
+                default:
+                    /* No other NSH encap properties defined yet. */
+                    break;
+            }
+        }
+        ptr += ROUND_UP(prop_ptr->len, 8);
+    }
+    if (buf->size == 0 || buf->size >= OVS_ENCAP_NSH_MAX_MD_LEN) {
+        ofpbuf_delete(buf);
+        buf = NULL;
+    }
+
+    /* Determine the Next Protocol field for NSH header. */
+    switch (ntohl(packet_type)) {
+        case PT_ETH:
+            np = NSH_P_ETHERNET;
+            break;
+        case PT_IPV4:
+            np = NSH_P_IPV4;
+            break;
+        case PT_IPV6:
+            np = NSH_P_IPV6;
+            break;
+        case PT_NSH:
+            np = NSH_P_NSH;
+            break;
+        default:
+            /* Error handling: drop packet. */
+            xlate_report_debug(ctx, OFT_ACTION,
+                               "Dropping packet as encap(nsh) is not "
+                               "supported for packet type (%d,0x%x)",
+                               pt_ns(packet_type), pt_ns_type(packet_type));
+            ctx->error = 1;
+            return buf;
+    }
+    /* Note that we have matched on packet_type! */
+    wc->masks.packet_type = OVS_BE32_MAX;
+
+    /* Reset all current flow packet headers. */
+    memset(&flow->dl_dst, 0, sizeof(struct flow) - offsetof(struct flow, dl_dst));
+
+    /* 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.np = np;
+    flow->nsh.spi = 0;
+    flow->nsh.si = 255;
+
+    if (md_type == NSH_M_TYPE1) {
+        flow->nsh.mdtype = NSH_M_TYPE1;
+        flow->nsh.c1 = 0;
+        flow->nsh.c2 = 0;
+        flow->nsh.c3 = 0;
+        flow->nsh.c4 = 0;
+        if (buf) {
+            /* Drop any MD2 context TLVs. */
+            ofpbuf_delete(buf);
+            buf = NULL;
+        }
+    } else if (md_type == NSH_M_TYPE2) {
+        flow->nsh.mdtype = NSH_M_TYPE2;
+    }
+
+    return buf;
+}
+
 static void
 xlate_generic_encap_action(struct xlate_ctx *ctx,
                            const struct ofpact_encap *encap)
 {
     struct flow *flow = &ctx->xin->flow;
     struct flow_wildcards *wc = ctx->wc;
+    struct ofpbuf *encap_data = NULL;
 
     /* Ensure that any pending actions on the inner packet are applied before
      * rewriting the flow */
@@ -5734,15 +5852,19 @@  xlate_generic_encap_action(struct xlate_ctx *ctx,
         case PT_ETH:
             rewrite_flow_encap_ethernet(ctx, flow, wc);
             break;
+        case PT_NSH:
+            encap_data = rewrite_flow_encap_nsh(ctx, encap, flow, wc);
+            break;
         default:
-            /* TODO: Error handling: Should not happen if the PT is checked
-             * at decoding */
+            /* New packet type was checked during decoding. */
+            OVS_NOT_REACHED();
             break;
     }
 
     if (!ctx->error) {
         /* The actual encap datapath action will be generated at next commit. */
         ctx->pending_encap = true;
+        ctx->encap_data = encap_data;
     }
 }
 
@@ -5773,11 +5895,42 @@  xlate_generic_decap_action(struct xlate_ctx *ctx,
                 ctx->wc->masks.dl_type = OVS_BE16_MAX;
             }
             return false;
+        case PT_NSH:
+            /* The decap_nsh action is generated at the commit executed as
+             * part of freezing the ctx for recirculation. Here we just set
+             * the new packet type based on the NSH next protocol field. */
+            switch (flow->nsh.np) {
+            case NSH_P_ETHERNET:
+                flow->packet_type = htonl(PT_ETH);
+                break;
+            case NSH_P_IPV4:
+                flow->packet_type = htonl(PT_IPV4);
+                break;
+            case NSH_P_IPV6:
+                flow->packet_type = htonl(PT_IPV6);
+                break;
+            case NSH_P_NSH:
+                flow->packet_type = htonl(PT_NSH);
+                break;
+            default:
+                /* Error handling: drop packet. */
+                xlate_report_debug(ctx, OFT_ACTION,
+                                   "Dropping packet as NSH next protocol %d "
+                                   "is not supported", flow->nsh.np);
+                ctx->error = 1;
+                return false;
+                break;
+            }
+            ctx->wc->masks.nsh.np = UINT8_MAX;
+            /* Trigger recirculation. */
+            return true;
         default:
-            xlate_report_debug(ctx, OFT_ACTION,
-                               "decap() for unsupported packet type %x",
-                               ntohl(flow->packet_type));
-            /* TODO: Error handling: drop packet. */
+            /* Error handling: drop packet. */
+            xlate_report_debug(
+                    ctx, OFT_ACTION,
+                    "Dropping packet as the decap() does not support "
+                    "packet type (%d,0x%x)",
+                    pt_ns(flow->packet_type), pt_ns_type(flow->packet_type));
             ctx->error = 1;
             return false;
     }
@@ -6623,6 +6776,7 @@  xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
         .in_action_set = false,
         .in_packet_out = xin->in_packet_out,
         .pending_encap = false,
+        .encap_data = NULL,
 
         .table_id = 0,
         .rule_cookie = OVS_BE64_MAX,
@@ -6971,6 +7125,7 @@  exit:
     ofpbuf_uninit(&ctx.action_set);
     ofpbuf_uninit(&ctx.frozen_actions);
     ofpbuf_uninit(&scratch_actions);
+    ofpbuf_delete(ctx.encap_data);
 
     /* Make sure we return a "drop flow" in case of an error. */
     if (ctx.error) {
diff --git a/tests/nsh.at b/tests/nsh.at
index 18ebda6..68ff374 100644
--- a/tests/nsh.at
+++ b/tests/nsh.at
@@ -17,25 +17,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=1193046,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_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
 ])
 
 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'
 ], [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=1193046,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_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=1193046,nsh_si=255,nsh_c1=0x11223344, priority 32768
+ 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
     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=1193046,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=1193046,nsh_si=255,nsh_c1=0x11223344
-Datapath actions: set(nsh(flags=128,spi=0x00123456,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=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
 ])
 
 OVS_VSWITCHD_STOP