diff --git a/NEWS b/NEWS
index facea02..ca02ca7 100644
--- a/NEWS
+++ b/NEWS
@@ -62,11 +62,17 @@ Post-v2.7.0
      * The "learn" action now supports a "limit" option (see ovs-ofctl(8)).
      * The port status bit OFPPS_LIVE now reflects link aliveness.
      * OpenFlow 1.5 packet-out is now supported.
+     * Support for OpenFlow 1.5 field packet_type and packet-type-aware
+       pipeline (PTAP).
+     * Added generic encap and decap actions (EXT-382).
+       First supported use case is encap/decap for Ethernet.
    - Fedora Packaging:
      * OVN services are no longer restarted automatically after upgrade.
    - Add --cleanup option to command 'ovs-appctl exit' (see ovs-vswitchd(8)).
    - L3 tunneling:
      * Use new tunnel port option "packet_type" to configure L2 vs. L3.
+     * In conjunction with PTAP tunnel ports can handle a mix of L2 and L3
+       payload.
      * New vxlan tunnel extension "gpe" to support VXLAN-GPE tunnels.
      * New support for non-Ethernet (L3) payloads in GRE and VXLAN-GPE.
    - The BFD detection multiplier is now user-configurable.
diff --git a/include/openflow/openflow-common.h b/include/openflow/openflow-common.h
index 5f1e225..d665519 100644
--- a/include/openflow/openflow-common.h
+++ b/include/openflow/openflow-common.h
@@ -466,6 +466,7 @@ enum ofp_header_type_namespaces {
     OFPHTN_IP_PROTO = 2,        /* ns_type is a IP protocol number. */
     OFPHTN_UDP_TCP_PORT = 3,    /* ns_type is a TCP or UDP port. */
     OFPHTN_IPV4_OPTION = 4,     /* ns_type is an IPv4 option number. */
+    OFPHTN_N_TYPES
 };
 
 #endif /* openflow/openflow-common.h */
diff --git a/include/openvswitch/automake.mk b/include/openvswitch/automake.mk
index 6bace61..0a139d0 100644
--- a/include/openvswitch/automake.mk
+++ b/include/openvswitch/automake.mk
@@ -12,6 +12,7 @@ openvswitchinclude_HEADERS = \
 	include/openvswitch/meta-flow.h \
 	include/openvswitch/ofpbuf.h \
 	include/openvswitch/ofp-actions.h \
+	include/openvswitch/ofp-ed-props.h \
 	include/openvswitch/ofp-errors.h \
 	include/openvswitch/ofp-msgs.h \
 	include/openvswitch/ofp-parse.h \
diff --git a/include/openvswitch/ofp-actions.h b/include/openvswitch/ofp-actions.h
index 198107e..4357f4e 100644
--- a/include/openvswitch/ofp-actions.h
+++ b/include/openvswitch/ofp-actions.h
@@ -25,6 +25,7 @@
 #include "openvswitch/ofp-util.h"
 #include "openvswitch/ofp-errors.h"
 #include "openvswitch/types.h"
+#include "openvswitch/ofp-ed-props.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -93,6 +94,10 @@ struct vl_mff_map;
     OFPACT(PUSH_MPLS,       ofpact_push_mpls,   ofpact, "push_mpls")    \
     OFPACT(POP_MPLS,        ofpact_pop_mpls,    ofpact, "pop_mpls")     \
                                                                         \
+    /* Generic encap & decap */                                         \
+    OFPACT(ENCAP,           ofpact_encap,       props, "encap")         \
+    OFPACT(DECAP,           ofpact_decap,       ofpact, "decap")        \
+                                                                        \
     /* Metadata. */                                                     \
     OFPACT(SET_TUNNEL,      ofpact_tunnel,      ofpact, "set_tunnel")   \
     OFPACT(SET_QUEUE,       ofpact_queue,       ofpact, "set_queue")    \
@@ -974,6 +979,33 @@ struct ofpact_unroll_xlate {
     ovs_be64 rule_cookie;         /* OVS_BE64_MAX if none. */
 };
 
+/* OFPACT_ENCAP.
+ *
+ * Used for NXAST_ENCAP. */
+
+struct ofpact_encap {
+    struct ofpact ofpact;
+    ovs_be32 new_pkt_type;        /* Packet type of the header to add. */
+    uint16_t hdr_size;            /* New header size in bytes. */
+    uint16_t n_props;             /* Number of encap properties. */
+    struct ofpact_ed_prop props[]; /* Properties in internal format. */
+};
+
+/* OFPACT_DECAP.
+ *
+ * Used for NXAST_DECAP. */
+struct ofpact_decap {
+    struct ofpact ofpact;
+
+    /* New packet type.
+     *
+     * The special value (0,0xFFFE) "Use next proto" is used to request OVS to
+     * automatically set the new packet type based on the decap'ed header's
+     * next protocol.
+     */
+    ovs_be32 new_pkt_type;
+};
+
 /* Converting OpenFlow to ofpacts. */
 enum ofperr ofpacts_pull_openflow_actions(struct ofpbuf *openflow,
                                           unsigned int actions_len,
diff --git a/include/openvswitch/ofp-ed-props.h b/include/openvswitch/ofp-ed-props.h
new file mode 100644
index 0000000..b72298f
--- /dev/null
+++ b/include/openvswitch/ofp-ed-props.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2017 Intel, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OPENVSWITCH_OFP_ED_PROPS_H
+#define OPENVSWITCH_OFP_ED_PROPS_H 1
+
+#include "openvswitch/ofp-errors.h"
+#include "openvswitch/types.h"
+#include "openvswitch/ofpbuf.h"
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+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. */
+
+    /* Experimenter property class.
+     *
+     * First 32 bits of property data
+     * is exp id after that is the experimenter property data.
+     */
+    OFPPPC_EXPERIMENTER=0xffff
+};
+
+/*
+ * External representation of encap/decap properties.
+ * These must be padded to a multiple of 8 bytes.
+ */
+struct ofp_ed_prop_header {
+    ovs_be16 prop_class;
+    uint8_t type;
+    uint8_t len;
+};
+
+/*
+ * Internal representation of encap/decap properties
+ */
+struct ofpact_ed_prop {
+    uint16_t prop_class;
+    uint8_t type;
+    uint8_t len;
+};
+
+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,
+                           struct ofpbuf *out);
+bool parse_ed_prop_class(const char *str, uint16_t *prop_class);
+bool parse_ed_prop_type(uint16_t prop_class, const char *str, uint8_t *type);
+char *parse_ed_prop_value(uint16_t prop_class, uint8_t prop_type,
+                          const char *str, struct ofpbuf *out);
+char *format_ed_prop_class(const struct ofpact_ed_prop *prop);
+char *format_ed_prop_type(const struct ofpact_ed_prop *prop);
+void format_ed_prop_value(struct ds *s, const struct ofpact_ed_prop *prop);
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif /* ofp-ed-props.h */
diff --git a/include/openvswitch/ofp-errors.h b/include/openvswitch/ofp-errors.h
index b551ce6..3d09be3 100644
--- a/include/openvswitch/ofp-errors.h
+++ b/include/openvswitch/ofp-errors.h
@@ -276,6 +276,15 @@ enum ofperr {
      * 64. */
     OFPERR_NXBAC_BAD_CONJUNCTION,
 
+    /* NX1.3+(39).  Unsupported packet type in encap or decap. */
+    OFPERR_NXBAC_BAD_HEADER_TYPE,
+
+    /* NX1.3+(40).  Unrecognized encap or decap property. */
+    OFPERR_NXBAC_UNKNOWN_ED_PROP,
+
+    /* NX1.3+(41).  Error in encap or decap property. */
+    OFPERR_NXBAC_BAD_ED_PROP,
+
 /* ## --------------------- ## */
 /* ## OFPET_BAD_INSTRUCTION ## */
 /* ## --------------------- ## */
diff --git a/lib/automake.mk b/lib/automake.mk
index 54a1032..bd56f43 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -149,6 +149,7 @@ lib_libopenvswitch_la_SOURCES = \
 	lib/odp-util.c \
 	lib/odp-util.h \
 	lib/ofp-actions.c \
+	lib/ofp-ed-props.c \
 	lib/ofp-errors.c \
 	lib/ofp-msgs.c \
 	lib/ofp-parse.c \
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 9d95d53..6390734 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -5922,13 +5922,17 @@ put_ethernet_key(const struct ovs_key_ethernet *eth, struct flow *flow)
 }
 
 static void
-commit_set_ether_addr_action(const struct flow *flow, struct flow *base_flow,
-                             struct ofpbuf *odp_actions,
-                             struct flow_wildcards *wc,
-                             bool use_masked)
+commit_set_ether_action(const struct flow *flow, struct flow *base_flow,
+                        struct ofpbuf *odp_actions,
+                        struct flow_wildcards *wc,
+                        bool use_masked)
 {
     struct ovs_key_ethernet key, base, mask;
 
+    if (flow->packet_type != htonl(PT_ETH)) {
+        return;
+    }
+
     get_ethernet_key(flow, &key);
     get_ethernet_key(base_flow, &base);
     get_ethernet_key(&wc->masks, &mask);
@@ -5941,29 +5945,6 @@ commit_set_ether_addr_action(const struct flow *flow, struct flow *base_flow,
 }
 
 static void
-commit_ether_action(const struct flow *flow, struct flow *base_flow,
-                    struct ofpbuf *odp_actions, struct flow_wildcards *wc,
-                    bool use_masked)
-{
-    if (flow->packet_type == htonl(PT_ETH)) {
-        if (base_flow->packet_type != htonl(PT_ETH)) {
-            odp_put_push_eth_action(odp_actions, &flow->dl_src, &flow->dl_dst);
-            base_flow->packet_type = flow->packet_type;
-            base_flow->dl_src = flow->dl_src;
-            base_flow->dl_dst = flow->dl_dst;
-        } else {
-            commit_set_ether_addr_action(flow, base_flow, odp_actions, wc,
-                                         use_masked);
-        }
-    } else {
-        if (base_flow->packet_type == htonl(PT_ETH)) {
-            odp_put_pop_eth_action(odp_actions);
-            base_flow->packet_type = flow->packet_type;
-        }
-    }
-}
-
-static void
 commit_vlan_action(const struct flow* flow, struct flow *base,
                    struct ofpbuf *odp_actions, struct flow_wildcards *wc)
 {
@@ -6400,6 +6381,50 @@ commit_set_pkt_mark_action(const struct flow *flow, struct flow *base_flow,
     }
 }
 
+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)
+{
+    if (flow->packet_type == base_flow->packet_type) {
+        return;
+    }
+
+    if (pending_encap) {
+        switch (ntohl(flow->packet_type)) {
+        case PT_ETH: {
+            /* push_eth */
+            odp_put_push_eth_action(odp_actions, &flow->dl_src,
+                                    &flow->dl_dst);
+            base_flow->packet_type = flow->packet_type;
+            base_flow->dl_src = flow->dl_src;
+            base_flow->dl_dst = flow->dl_dst;
+            break;
+        }
+        default:
+            /* Only the above protocols are supported for encap. The check
+             * is done at action decoding. */
+            OVS_NOT_REACHED();
+        }
+    } else {
+        if (pt_ns(flow->packet_type) == OFPHTN_ETHERTYPE &&
+            base_flow->packet_type == htonl(PT_ETH)) {
+            /* pop_eth */
+            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();
+        }
+    }
+
+    wc->masks.packet_type = OVS_BE32_MAX;
+}
+
 /* 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
@@ -6412,12 +6437,13 @@ commit_set_pkt_mark_action(const struct flow *flow, struct flow *base_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 use_masked, bool pending_encap)
 {
     enum slow_path_reason slow1, slow2;
     bool mpls_done = false;
 
-    commit_ether_action(flow, base, odp_actions, wc, use_masked);
+    commit_packet_type_change(flow, base, odp_actions, wc, pending_encap);
+    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. */
     if (eth_type_mpls(base->dl_type) && !eth_type_mpls(flow->dl_type)) {
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 224f78e..bdb2354 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -276,7 +276,8 @@ enum slow_path_reason commit_odp_actions(const struct flow *,
                                          struct flow *base,
                                          struct ofpbuf *odp_actions,
                                          struct flow_wildcards *wc,
-                                         bool use_masked);
+                                         bool use_masked,
+                                         bool pending_encap);
 
 /* ofproto-dpif interface.
  *
diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
index ae27d9d..d0437f2 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -342,6 +342,12 @@ enum ofp_raw_action_type {
     /* NX1.0+(43): void. */
     NXAST_RAW_CT_CLEAR,
 
+    /* NX1.3+(46): struct nx_action_encap, ... */
+    NXAST_RAW_ENCAP,
+
+    /* NX1.3+(47): struct nx_action_decap, ... */
+    NXAST_RAW_DECAP,
+
 /* ## ------------------ ## */
 /* ## Debugging actions. ## */
 /* ## ------------------ ## */
@@ -472,6 +478,8 @@ ofpact_next_flattened(const struct ofpact *ofpact)
     case OFPACT_WRITE_METADATA:
     case OFPACT_GOTO_TABLE:
     case OFPACT_NAT:
+    case OFPACT_ENCAP:
+    case OFPACT_DECAP:
         return ofpact_next(ofpact);
 
     case OFPACT_CLONE:
@@ -4019,6 +4027,320 @@ format_FIN_TIMEOUT(const struct ofpact_fin_timeout *a,
     ds_chomp(s, ',');
     ds_put_format(s, "%s)%s", colors.paren, colors.end);
 }
+
+/* Action structure for NXAST_ENCAP */
+struct nx_action_encap {
+    ovs_be16 type;         /* OFPAT_VENDOR. */
+    ovs_be16 len;          /* Total size including any property TLVs. */
+    ovs_be32 vendor;       /* NX_VENDOR_ID. */
+    ovs_be16 subtype;      /* NXAST_ENCAP. */
+    ovs_be16 hdr_size;     /* Header size in bytes, 0 = 'not specified'.*/
+    ovs_be32 new_pkt_type; /* Header type to add and PACKET_TYPE of result. */
+    ovs_be16 n_props;      /* Number of the following endecap properties. */
+    uint8_t pad[6];        /* Align to 8 bytes boundary */
+    struct ofp_ed_prop_header props[];  /* Encap TLV properties. */
+};
+OFP_ASSERT(sizeof(struct nx_action_encap) == 24);
+
+static enum ofperr
+decode_NXAST_RAW_ENCAP(const struct nx_action_encap *nae,
+                       enum ofp_version ofp_version OVS_UNUSED,
+                       struct ofpbuf *out)
+{
+    struct ofpact_encap *encap;
+    const struct ofp_ed_prop_header *ofp_prop;
+    size_t props_len;
+    uint16_t n_props = 0;
+    int err;
+
+    encap = ofpact_put_ENCAP(out);
+    encap->ofpact.raw = NXAST_RAW_ENCAP;
+    switch (ntohl(nae->new_pkt_type)) {
+    case PT_ETH:
+        /* Add supported encap header types here. */
+        break;
+    default:
+        return OFPERR_NXBAC_BAD_HEADER_TYPE;
+    }
+    encap->new_pkt_type = nae->new_pkt_type;
+    encap->hdr_size = ntohs(nae->hdr_size);
+    encap->n_props = ntohs(nae->n_props);
+
+    ofp_prop = nae->props;
+    props_len = ntohs(nae->len) - offsetof(struct nx_action_encap, props);
+    n_props = 0;
+    while (props_len > 0) {
+        err = decode_ed_prop(&ofp_prop, out, &props_len);
+        if (err) {
+            return err;
+        }
+        n_props++;
+    }
+    if (n_props != encap->n_props) {
+        return OFPERR_NXBAC_BAD_ED_PROP;
+    }
+    out->header = &encap->ofpact;
+    ofpact_finish_ENCAP(out, &encap);
+
+    return 0;
+}
+
+static void
+encode_ENCAP(const struct ofpact_encap *encap,
+             enum ofp_version ofp_version OVS_UNUSED,
+             struct ofpbuf *out)
+{
+    size_t start_ofs = out->size;
+    struct nx_action_encap *nae = put_NXAST_ENCAP(out);
+    int i;
+
+    nae->new_pkt_type = encap->new_pkt_type;
+    nae->hdr_size = htons(encap->hdr_size);
+    nae->n_props = htons(encap->n_props);
+    const struct ofpact_ed_prop *prop = encap->props;
+    for (i = 0; i < encap->n_props; i++) {
+        encode_ed_prop(&prop, out);
+    }
+    pad_ofpat(out, start_ofs);
+}
+
+static bool
+parse_encap_header(const char *hdr, ovs_be32 *packet_type)
+{
+    if (strcmp(hdr, "ethernet") == 0) {
+        *packet_type = htonl(PT_ETH);
+    } else {
+        return false;
+    }
+    return true;
+}
+
+static char *
+parse_ed_props(char **arg, int *n_props, struct ofpbuf *out)
+{
+    char *key, *value, *err, *prop, *str;
+    uint16_t prop_class;
+    uint8_t prop_type;
+
+    str = *arg;
+    while (ofputil_parse_key_value(arg, &key, &value)) {
+        if (strcmp(key, "prop") != 0) {
+            return xasprintf("Invalid encap argument: %s", key);
+        }
+        /* Parse property class. */
+        str = prop = value;
+        if (!ofputil_parse_key_value(&prop, &key, &value)
+            || strcmp(key, "class") != 0 || value == NULL) {
+            return xasprintf("Missing prop class: %s", str);
+        }
+        if (!parse_ed_prop_class(value, &prop_class)) {
+            return xasprintf("Invalid encap prop class: %s", value);
+        }
+        /* Parse property type. */
+        str = prop;
+        if (!ofputil_parse_key_value(&prop, &key, &value)
+            || strcmp(key, "type") != 0 || value == NULL) {
+            return xasprintf("Missing encap prop type: %s", str);
+        }
+        if (!parse_ed_prop_type(prop_class, value, &prop_type)) {
+            return xasprintf("Invalid property type: %s", value);
+        }
+        /* Parse the prop value dependent on class and type. */
+        str = prop;
+        if (!ofputil_parse_key_value(&prop, &key, &value)
+            || strcmp(key, "val") != 0 || value == NULL) {
+            return xasprintf("Missing encap prop value: %s", str);
+        }
+        err = parse_ed_prop_value(prop_class, prop_type, value, out);
+        if (err != NULL) {
+            return err;
+        }
+        (*n_props)++;
+    }
+    return NULL;
+}
+
+/* The string representation of the encap action is
+ * encap(hdr=<pkt_type>,prop(class=<class>,type=<type>,val=<val>),prop(...),...)
+ */
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_ENCAP(char *arg,
+            const struct ofputil_port_map *port_map OVS_UNUSED,
+            struct ofpbuf *out,
+            enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+    struct ofpact_encap *encap;
+    char *key, *value, *str;
+    char *error = NULL;
+    int n_props = 0;
+
+    encap = ofpact_put_ENCAP(out);
+    encap->hdr_size = 0;
+    /* Parse encap header type. */
+    str = arg;
+    if (!ofputil_parse_key_value(&arg, &key, &value)
+        || strcmp(key, "hdr") != 0 || value == NULL) {
+        return xasprintf("Missing encap hdr: %s", str);
+    }
+    if (!parse_encap_header(value, &encap->new_pkt_type)) {
+        return xasprintf("Encap hdr not supported: %s", value);
+    }
+    /* Parse encap properties. */
+    error = parse_ed_props(&arg, &n_props, out);
+    if (error != NULL) {
+        return error;
+    }
+    /* ofbuf out may have been re-allocated. */
+    encap = out->header;
+    encap->n_props = n_props;
+    ofpact_finish_ENCAP(out, &encap);
+    return NULL;
+}
+
+static char *
+format_encap_pkt_type(const ovs_be32 pkt_type)
+{
+    switch (ntohl(pkt_type)) {
+    case PT_ETH:
+        return "ethernet";
+    default:
+        return "UNKNOWN";
+    }
+}
+
+static void
+format_ed_props(struct ds *s, uint16_t n_props,
+                const struct ofpact_ed_prop *prop)
+{
+    const uint8_t *p = (uint8_t *) prop;
+    int i;
+
+    if (n_props == 0) {
+        return;
+    }
+    for (i = 0; i < n_props; i++) {
+        ds_put_format(s, ",prop(class=%s,", format_ed_prop_class(prop));
+        ds_put_format(s, "type=%s,", format_ed_prop_type(prop));
+        format_ed_prop_value(s, prop);
+        ds_put_cstr(s, ")");
+        p += ROUND_UP(prop->len, 8);
+        prop = ALIGNED_CAST(const struct ofpact_ed_prop *, p);
+    }
+}
+
+static void
+format_ENCAP(const struct ofpact_encap *a,
+             const struct ofputil_port_map *port_map OVS_UNUSED,
+             struct ds *s)
+{
+    ds_put_format(s, "%sencap(%s", colors.paren, colors.end);
+    ds_put_format(s, "hdr=%s", format_encap_pkt_type(a->new_pkt_type));
+    format_ed_props(s, a->n_props, a->props);
+    ds_put_format(s, "%s)%s", colors.paren, colors.end);
+}
+
+/* Action structure for NXAST_DECAP */
+struct nx_action_decap {
+    ovs_be16 type;         /* OFPAT_VENDOR. */
+    ovs_be16 len;          /* Total size including any property TLVs. */
+    ovs_be32 vendor;       /* NX_VENDOR_ID. */
+    ovs_be16 subtype;      /* NXAST_DECAP. */
+    uint8_t pad[2];        /* 2 bytes padding */
+
+    /* Packet type or result.
+     *
+     * The special value (0,0xFFFE) "Use next proto"
+     * is used to request OvS to automatically set the new packet type based
+     * on the decap'ed header's next protocol.
+     */
+    ovs_be32 new_pkt_type;
+};
+OFP_ASSERT(sizeof(struct nx_action_decap) == 16);
+
+static enum ofperr
+decode_NXAST_RAW_DECAP(const struct nx_action_decap *nad,
+                       enum ofp_version ofp_version OVS_UNUSED,
+                       struct ofpbuf *ofpacts)
+{
+    struct ofpact_decap * decap;
+
+    decap = ofpact_put_DECAP(ofpacts);
+    decap->ofpact.raw = NXAST_RAW_DECAP;
+    decap->new_pkt_type = nad->new_pkt_type;
+    return 0;
+}
+
+static void
+encode_DECAP(const struct ofpact_decap *decap,
+                enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out)
+{
+    struct nx_action_decap *nad = put_NXAST_DECAP(out);
+
+    nad->len = htons(sizeof(struct nx_action_decap));
+    nad->new_pkt_type = decap->new_pkt_type;
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_DECAP(char *arg,
+            const struct ofputil_port_map *port_map OVS_UNUSED,
+            struct ofpbuf *ofpacts,
+            enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+    struct ofpact_decap *decap;
+    char *key, *value, *pos;
+    char *error = NULL;
+    uint16_t ns, type;
+
+    decap = ofpact_put_DECAP(ofpacts);
+    /* Default next packet_type is PT_USE_NEXT_PROTO. */
+    decap->new_pkt_type = htonl(PT_USE_NEXT_PROTO);
+
+    /* Parse decap packet_type if given. */
+    if (ofputil_parse_key_value(&arg, &key, &value)) {
+        if (strcmp(key, "packet_type") == 0) {
+            pos = value;
+            if (!ofputil_parse_key_value(&pos, &key, &value)
+                || strcmp(key, "ns") != 0) {
+                return xstrdup("Missing packet_type attribute ns");
+            }
+            error = str_to_u16(value, "ns", &ns);
+            if (error) {
+                return error;
+            }
+            if (ns >= OFPHTN_N_TYPES) {
+                return xasprintf("Unsupported ns value: %"PRIu16, ns);
+            }
+            if (!ofputil_parse_key_value(&pos, &key, &value)
+                || strcmp(key, "type") != 0) {
+                return xstrdup("Missing packet_type attribute type");
+            }
+            error = str_to_u16(value, "type", &type);
+            if (error) {
+                return error;
+            }
+            decap->new_pkt_type = htonl(PACKET_TYPE(ns, type));
+        } else {
+            return xasprintf("Invalid decap argument: %s", key);
+        }
+    }
+    return NULL;
+}
+
+static void
+format_DECAP(const struct ofpact_decap *a,
+             const struct ofputil_port_map *port_map OVS_UNUSED,
+             struct ds *s)
+{
+    ds_put_format(s, "%sdecap(%s", colors.paren, colors.end);
+    if (a->new_pkt_type != htonl(PT_USE_NEXT_PROTO)) {
+        ds_put_format(s, "packet_type(ns=%"PRIu16",id=%#"PRIx16")",
+                      pt_ns(a->new_pkt_type),
+                      pt_ns_type(a->new_pkt_type));
+    }
+    ds_put_format(s, "%s)%s", colors.paren, colors.end);
+}
+
 
 /* Action structures for NXAST_RESUBMIT, NXAST_RESUBMIT_TABLE, and
  * NXAST_RESUBMIT_TABLE_CT.
@@ -6802,6 +7124,8 @@ ofpact_is_set_or_move_action(const struct ofpact *a)
     case OFPACT_SET_TUNNEL:
     case OFPACT_SET_VLAN_PCP:
     case OFPACT_SET_VLAN_VID:
+    case OFPACT_ENCAP:
+    case OFPACT_DECAP:
         return true;
     case OFPACT_BUNDLE:
     case OFPACT_CLEAR_ACTIONS:
@@ -6877,6 +7201,8 @@ ofpact_is_allowed_in_actions_set(const struct ofpact *a)
     case OFPACT_SET_VLAN_PCP:
     case OFPACT_SET_VLAN_VID:
     case OFPACT_STRIP_VLAN:
+    case OFPACT_ENCAP:
+    case OFPACT_DECAP:
         return true;
 
     /* In general these actions are excluded because they are not part of
@@ -6984,6 +7310,8 @@ ofpacts_execute_action_set(struct ofpbuf *action_list,
     /* The OpenFlow spec "Action Set" section specifies this order. */
     ofpacts_copy_last(action_list, action_set, OFPACT_STRIP_VLAN);
     ofpacts_copy_last(action_list, action_set, OFPACT_POP_MPLS);
+    ofpacts_copy_last(action_list, action_set, OFPACT_DECAP);
+    ofpacts_copy_last(action_list, action_set, OFPACT_ENCAP);
     ofpacts_copy_last(action_list, action_set, OFPACT_PUSH_MPLS);
     ofpacts_copy_last(action_list, action_set, OFPACT_PUSH_VLAN);
     ofpacts_copy_last(action_list, action_set, OFPACT_DEC_TTL);
@@ -7127,6 +7455,8 @@ ovs_instruction_type_from_ofpact_type(enum ofpact_type type)
     case OFPACT_CT:
     case OFPACT_CT_CLEAR:
     case OFPACT_NAT:
+    case OFPACT_ENCAP:
+    case OFPACT_DECAP:
     default:
         return OVSINST_OFPIT11_APPLY_ACTIONS;
     }
@@ -7488,8 +7818,8 @@ inconsistent_match(enum ofputil_protocol *usable_protocols)
     *usable_protocols &= OFPUTIL_P_OF10_ANY;
 }
 
-/* May modify flow->dl_type, flow->nw_proto and flow->vlan_tci,
- * caller must restore them.
+/* May modify flow->packet_type, flow->dl_type, flow->nw_proto and
+ * flow->vlan_tci, caller must restore them.
  *
  * Modifies some actions, filling in fields that could not be properly set
  * without context. */
@@ -7501,6 +7831,7 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
     struct flow *flow = &match->flow;
     const struct ofpact_enqueue *enqueue;
     const struct mf_field *mf;
+    ovs_be16 dl_type = get_dl_type(flow);
 
     switch (a->type) {
     case OFPACT_OUTPUT:
@@ -7578,7 +7909,7 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
 
     case OFPACT_SET_IPV4_SRC:
     case OFPACT_SET_IPV4_DST:
-        if (flow->dl_type != htons(ETH_TYPE_IP)) {
+        if (dl_type != htons(ETH_TYPE_IP)) {
             inconsistent_match(usable_protocols);
         }
         return 0;
@@ -7643,7 +7974,7 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
     case OFPACT_SET_MPLS_TC:
     case OFPACT_SET_MPLS_TTL:
     case OFPACT_DEC_MPLS_TTL:
-        if (!eth_type_mpls(flow->dl_type)) {
+        if (!eth_type_mpls(dl_type)) {
             inconsistent_match(usable_protocols);
         }
         return 0;
@@ -7681,6 +8012,9 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
         return 0;
 
     case OFPACT_PUSH_MPLS:
+        if (flow->packet_type != htonl(PT_ETH)) {
+            inconsistent_match(usable_protocols);
+        }
         flow->dl_type = ofpact_get_PUSH_MPLS(a)->ethertype;
         /* The packet is now MPLS and the MPLS payload is opaque.
          * Thus nothing can be assumed about the network protocol.
@@ -7689,7 +8023,8 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
         return 0;
 
     case OFPACT_POP_MPLS:
-        if (!eth_type_mpls(flow->dl_type)) {
+        if (flow->packet_type != htonl(PT_ETH)
+            || !eth_type_mpls(dl_type)) {
             inconsistent_match(usable_protocols);
         }
         flow->dl_type = ofpact_get_POP_MPLS(a)->ethertype;
@@ -7708,7 +8043,7 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
     case OFPACT_CT: {
         struct ofpact_conntrack *oc = ofpact_get_CT(a);
 
-        if (!dl_type_is_ip_any(flow->dl_type)
+        if (!dl_type_is_ip_any(dl_type)
             || (flow->ct_state & CS_INVALID && oc->flags & NX_CT_F_COMMIT)
             || (oc->alg == IPPORT_FTP && flow->nw_proto != IPPROTO_TCP)
             || (oc->alg == IPPORT_TFTP && flow->nw_proto != IPPROTO_UDP)) {
@@ -7733,10 +8068,10 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
     case OFPACT_NAT: {
         struct ofpact_nat *on = ofpact_get_NAT(a);
 
-        if (!dl_type_is_ip_any(flow->dl_type) ||
-            (on->range_af == AF_INET && flow->dl_type != htons(ETH_TYPE_IP)) ||
+        if (!dl_type_is_ip_any(dl_type) ||
+            (on->range_af == AF_INET && dl_type != htons(ETH_TYPE_IP)) ||
             (on->range_af == AF_INET6
-             && flow->dl_type != htons(ETH_TYPE_IPV6))) {
+             && dl_type != htons(ETH_TYPE_IPV6))) {
             return OFPERR_OFPBAC_MATCH_INCONSISTENT;
         }
         return 0;
@@ -7785,6 +8120,29 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
     case OFPACT_DEBUG_RECIRC:
         return 0;
 
+    case OFPACT_ENCAP:
+        flow->packet_type = ofpact_get_ENCAP(a)->new_pkt_type;
+        if (pt_ns(flow->packet_type) == OFPHTN_ETHERTYPE) {
+            flow->dl_type = htons(pt_ns_type(flow->packet_type));
+        }
+        if (!is_ip_any(flow)) {
+            flow->nw_proto = 0;
+        }
+        return 0;
+
+    case OFPACT_DECAP:
+        if (flow->packet_type == htonl(PT_ETH)) {
+            /* Adjust the packet_type to allow subsequent actions. */
+            flow->packet_type = PACKET_TYPE_BE(OFPHTN_ETHERTYPE,
+                                               ntohs(flow->dl_type));
+        } else {
+            /* The actual packet_type is only known after decapsulation.
+             * Do not allow subsequent actions that depend on packet headers. */
+            flow->packet_type = htonl(PT_UNKNOWN);
+            flow->dl_type = OVS_BE16_MAX;
+        }
+        return 0;
+
     default:
         OVS_NOT_REACHED();
     }
@@ -7810,6 +8168,7 @@ ofpacts_check(struct ofpact ofpacts[], size_t ofpacts_len,
               enum ofputil_protocol *usable_protocols)
 {
     struct ofpact *a;
+    ovs_be32 packet_type = match->flow.packet_type;
     ovs_be16 dl_type = match->flow.dl_type;
     uint8_t nw_proto = match->flow.nw_proto;
     enum ofperr error = 0;
@@ -7825,6 +8184,7 @@ ofpacts_check(struct ofpact ofpacts[], size_t ofpacts_len,
         }
     }
     /* Restore fields that may have been modified. */
+    match->flow.packet_type = packet_type;
     match->flow.dl_type = dl_type;
     memcpy(&match->flow.vlans, &vlans, sizeof(vlans));
     match->flow.nw_proto = nw_proto;
@@ -8276,6 +8636,8 @@ ofpact_outputs_to_port(const struct ofpact *ofpact, ofp_port_t port)
     case OFPACT_CT:
     case OFPACT_CT_CLEAR:
     case OFPACT_NAT:
+    case OFPACT_ENCAP:
+    case OFPACT_DECAP:
     default:
         return false;
     }
diff --git a/lib/ofp-ed-props.c b/lib/ofp-ed-props.c
new file mode 100644
index 0000000..260adc3
--- /dev/null
+++ b/lib/ofp-ed-props.c
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2017 Intel, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <arpa/inet.h>
+#include "openvswitch/ofp-ed-props.h"
+#include "openvswitch/ofp-util.h"
+#include "openvswitch/ofpbuf.h"
+#include "openvswitch/ofp-parse.h"
+#include "util.h"
+#include "lib/packets.h"
+
+
+enum ofperr
+decode_ed_prop(const struct ofp_ed_prop_header **ofp_prop,
+               struct ofpbuf *out OVS_UNUSED,
+               size_t *remaining)
+{
+    uint16_t prop_class = ntohs((*ofp_prop)->prop_class);
+    size_t len = (*ofp_prop)->len;
+    size_t pad_len = ROUND_UP(len, 8);
+
+    switch (prop_class) {
+    default:
+        return OFPERR_NXBAC_UNKNOWN_ED_PROP;
+    }
+
+    *remaining -= pad_len;
+    *ofp_prop = ALIGNED_CAST(const struct ofp_ed_prop_header *,
+                             ((char *)(*ofp_prop) + pad_len));
+    return 0;
+}
+
+enum ofperr
+encode_ed_prop(const struct ofpact_ed_prop **prop,
+               struct ofpbuf *out OVS_UNUSED)
+{
+    size_t prop_len;
+
+    switch ((*prop)->prop_class) {
+    default:
+        return OFPERR_OFPBAC_BAD_ARGUMENT;
+    }
+
+    *prop = ALIGNED_CAST(const struct ofpact_ed_prop *,
+                         ((char *)(*prop) + prop_len));
+    return 0;
+}
+
+bool
+parse_ed_prop_class(const char *str OVS_UNUSED,
+                    uint16_t *prop_class)
+{
+    if (!strcmp(str,"basic")) {
+        *prop_class = OFPPPC_BASIC;
+    } else if (!strcmp(str,"mpls")) {
+        *prop_class = OFPPPC_MPLS;
+    } else if (!strcmp(str,"gre")) {
+        *prop_class = OFPPPC_GRE;
+    } else if (!strcmp(str,"gtp")) {
+        *prop_class = OFPPPC_GTP;
+    } else {
+        return false;
+    }
+    return true;
+}
+
+bool
+parse_ed_prop_type(uint16_t prop_class,
+                   const char *str OVS_UNUSED,
+                   uint8_t *type OVS_UNUSED)
+{
+    switch (prop_class) {
+    default:
+        return false;
+    }
+}
+
+/* Parse the value of an encap/decap property based on property class
+ * and type and append the parsed property in internal format to the
+ * ofpbuf out.
+ * Returns a malloced string in the event of a parse error. The caller
+ * must free the string.
+ */
+
+char *
+parse_ed_prop_value(uint16_t prop_class, uint8_t prop_type OVS_UNUSED,
+                    const char *value, struct ofpbuf *out OVS_UNUSED)
+{
+
+    if (value == NULL || *value == '\0') {
+        return xstrdup("Value missing for encap property");
+    }
+
+    switch (prop_class) {
+    default:
+        /* Unsupported property classes rejected before. */
+        OVS_NOT_REACHED();
+    }
+
+    return NULL;
+}
+
+char *
+format_ed_prop_class(const struct ofpact_ed_prop *prop)
+{
+    switch (prop->prop_class) {
+    case OFPPPC_BASIC:
+        return "basic";
+    case OFPPPC_MPLS:
+        return "mpls";
+    case OFPPPC_GRE:
+        return "gre";
+    case OFPPPC_GTP:
+        return "gtp";
+    default:
+        OVS_NOT_REACHED();
+    }
+}
+
+char *
+format_ed_prop_type(const struct ofpact_ed_prop *prop)
+{
+    switch (prop->prop_class) {
+    default:
+        OVS_NOT_REACHED();
+    }
+}
+
+void
+format_ed_prop_value(struct ds *s OVS_UNUSED,
+                     const struct ofpact_ed_prop *prop)
+{
+    switch (prop->prop_class) {
+    default:
+        OVS_NOT_REACHED();
+    }
+}
diff --git a/lib/packets.h b/lib/packets.h
index a9d5e84..8287ca3 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -1265,7 +1265,8 @@ pt_ns_type(ovs_be32 packet_type)
 
 /* Well-known packet_type field values. */
 enum packet_type {
-    PT_ETH  = PACKET_TYPE(OFPHTN_ONF, 0x0000),  /* Default: Ethernet */
+    PT_ETH  = PACKET_TYPE(OFPHTN_ONF, 0x0000),  /* Default PT: Ethernet */
+    PT_USE_NEXT_PROTO = PACKET_TYPE(OFPHTN_ONF, 0xfffe),  /* Pseudo PT for decap. */
     PT_IPV4 = PACKET_TYPE(OFPHTN_ETHERTYPE, ETH_TYPE_IP),
     PT_IPV6 = PACKET_TYPE(OFPHTN_ETHERTYPE, ETH_TYPE_IPV6),
     PT_MPLS = PACKET_TYPE(OFPHTN_ETHERTYPE, ETH_TYPE_MPLS),
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index c103793..a2f5dc7 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -234,6 +234,8 @@ 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. */
 
     uint8_t table_id;           /* OpenFlow table ID where flow was found. */
     ovs_be64 rule_cookie;       /* Cookie of the rule being translated. */
@@ -3465,7 +3467,8 @@ 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);
+                                          use_masked, ctx->pending_encap);
+    ctx->pending_encap = false;
 }
 
 static void
@@ -5503,6 +5506,8 @@ freeze_unroll_actions(const struct ofpact *a, const struct ofpact *end,
         case OFPACT_METER:
         case OFPACT_SAMPLE:
         case OFPACT_CLONE:
+        case OFPACT_ENCAP:
+        case OFPACT_DECAP:
         case OFPACT_DEBUG_RECIRC:
         case OFPACT_CT:
         case OFPACT_CT_CLEAR:
@@ -5692,6 +5697,93 @@ compose_conntrack_action(struct xlate_ctx *ctx, struct ofpact_conntrack *ofc)
 }
 
 static void
+rewrite_flow_encap_ethernet(struct xlate_ctx *ctx,
+                            struct flow *flow,
+                            struct flow_wildcards *wc)
+{
+    wc->masks.packet_type = OVS_BE32_MAX;
+    if (pt_ns(flow->packet_type) == OFPHTN_ETHERTYPE) {
+        /* Only adjust the packet_type and zero the dummy Ethernet addresses. */
+        ovs_be16 ethertype = pt_ns_type_be(flow->packet_type);
+        flow->packet_type = htonl(PT_ETH);
+        flow->dl_src = eth_addr_zero;
+        flow->dl_dst = eth_addr_zero;
+        flow->dl_type = ethertype;
+    } else {
+        xlate_report_debug(ctx, OFT_ACTION,
+                           "encap(ethernet) unsupported for packet type "
+                           "ethernet");
+        /* TODO: Error handling: drop packet. */
+        ctx->error = 1;
+    }
+}
+
+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;
+
+    /* Ensure that any pending actions on the inner packet are applied before
+     * rewriting the flow */
+    xlate_commit_actions(ctx);
+
+    /* Rewrite the flow to reflect the effect of pushing the new encap header. */
+    switch (ntohl(encap->new_pkt_type)) {
+        case PT_ETH:
+            rewrite_flow_encap_ethernet(ctx, flow, wc);
+            break;
+        default:
+            /* TODO: Error handling: Should not happen if the PT is checked
+             * at decoding */
+            break;
+    }
+
+    if (!ctx->error) {
+        /* The actual encap datapath action will be generated at next commit. */
+        ctx->pending_encap = true;
+    }
+}
+
+/* Returns true if packet must be recirculated after decapsulation. */
+static bool
+xlate_generic_decap_action(struct xlate_ctx *ctx,
+                           const struct ofpact_decap *decap OVS_UNUSED)
+{
+    struct flow *flow = &ctx->xin->flow;
+
+    /* Ensure that any pending actions on the current packet are applied
+     * before generating the decap action. */
+    xlate_commit_actions(ctx);
+
+    /* We assume for now that the new_pkt_type is PT_USE_NEXT_PROTO. */
+    switch (ntohl(flow->packet_type)) {
+        case PT_ETH:
+            if (flow->vlans[0].tci & htons(VLAN_CFI)) {
+                /* Error handling: drop packet. */
+                xlate_report_debug(ctx, OFT_ACTION, "Dropping packet, cannot "
+                                   "decap Ethernet if VLAN is present.");
+                ctx->error = 1;
+            } else {
+                /* Just change the packet_type.
+                 * Delay generating pop_eth to the next commit. */
+                flow->packet_type = htonl(PACKET_TYPE(OFPHTN_ETHERTYPE,
+                                                      ntohs(flow->dl_type)));
+                ctx->wc->masks.dl_type = OVS_BE16_MAX;
+            }
+            return false;
+        default:
+            xlate_report_debug(ctx, OFT_ACTION,
+                               "decap() for unsupported packet type %x",
+                               ntohl(flow->packet_type));
+            /* TODO: Error handling: drop packet. */
+            ctx->error = 1;
+            return false;
+    }
+}
+
+static void
 recirc_for_mpls(const struct ofpact *a, struct xlate_ctx *ctx)
 {
     /* No need to recirculate if already exiting. */
@@ -5765,6 +5857,8 @@ recirc_for_mpls(const struct ofpact *a, struct xlate_ctx *ctx)
     case OFPACT_EXIT:
     case OFPACT_SAMPLE:
     case OFPACT_CLONE:
+    case OFPACT_ENCAP:
+    case OFPACT_DECAP:
     case OFPACT_UNROLL_XLATE:
     case OFPACT_CT:
     case OFPACT_CT_CLEAR:
@@ -6181,6 +6275,22 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             xlate_clone(ctx, ofpact_get_CLONE(a));
             break;
 
+        case OFPACT_ENCAP:
+            xlate_generic_encap_action(ctx, ofpact_get_ENCAP(a));
+            break;
+
+        case OFPACT_DECAP: {
+            bool recirc_needed =
+                    xlate_generic_decap_action(ctx, ofpact_get_DECAP(a));
+            if (!ctx->error && recirc_needed) {
+                /* Recirculate for parsing of inner packet. */
+                ctx_trigger_freeze(ctx);
+                /* Then continue with next action. */
+                a = ofpact_next(a);
+            }
+            break;
+        }
+
         case OFPACT_CT:
             compose_conntrack_action(ctx, ofpact_get_CT(a));
             break;
@@ -6423,7 +6533,7 @@ xlate_wc_finish(struct xlate_ctx *ctx)
      * use non-header fields as part of the cache. */
     flow_wildcards_clear_non_packet_fields(ctx->wc);
 
-    /* Wildcard ethernet addresses if the original packet type was not
+    /* Wildcard ethernet fields if the original packet type was not
      * Ethernet. */
     if (ctx->xin->upcall_flow->packet_type != htonl(PT_ETH)) {
         ctx->wc->masks.dl_dst = eth_addr_zero;
@@ -6512,6 +6622,7 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
         .in_group = false,
         .in_action_set = false,
         .in_packet_out = xin->in_packet_out,
+        .pending_encap = false,
 
         .table_id = 0,
         .rule_cookie = OVS_BE64_MAX,
@@ -6672,6 +6783,7 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
         flow->packet_type = htonl(PT_ETH);
         flow->dl_src = eth_addr_zero;
         flow->dl_dst = eth_addr_zero;
+        ctx.pending_encap = true;
     }
 
     if (!xin->ofpacts && !ctx.rule) {
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index 6552590..cfd8fee 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -1561,17 +1561,23 @@ the action set, the one written later replaces the earlier action:
 \fBpop_mpls\fR
 .
 .IP 2.
-\fBpush_mpls\fR
+\fBdecap\fR
 .
 .IP 3.
-\fBpush_vlan\fR
+\fBencap\fR
 .
 .IP 4.
+\fBpush_mpls\fR
+.
+.IP 5.
+\fBpush_vlan\fR
+.
+.IP 6.
 \fBdec_ttl\fR
 .IQ
 \fBdec_mpls_ttl\fR
 .
-.IP 5.
+.IP 7.
 \fBload\fR
 .IQ
 \fBmove\fR
@@ -1611,10 +1617,10 @@ the later modification takes effect, and when they modify
 different parts of a field (or different fields), then both
 modifications are applied.
 .
-.IP 6.
+.IP 8.
 \fBset_queue\fR
 .
-.IP 7.
+.IP 9.
 \fBgroup\fR
 .IQ
 \fBoutput\fR
@@ -1630,6 +1636,7 @@ not visible.)
 .RE
 .IP
 Only the actions listed above may be written to the action set.
+\fBencap\fR and \fBdecap\fR actions are nonstandard.
 .
 .IP \fBwrite_metadata\fB:\fIvalue\fR[/\fImask\fR]
 Updates the metadata field for the flow. If \fImask\fR is omitted, the
@@ -1730,6 +1737,62 @@ that is saved and restored includes all flow data and metadata
 \fBpush\fR and \fBpop\fR actions, and the OpenFlow action set.
 .IP
 This action was added in Open vSwitch 2.6.90.
+.
+.IP "\fBencap(hdr=\fR\fIheader\fR[\fB,prop(class=\fR\fIclass\fR\fB,type=\fR\fItype\fR\fB,val=\fR\fIvalue\fR\fB)\fR...]\fB)\fR"
+Encapsulates the packet with a new packet header, e.g., ethernet
+or nsh.
+.
+.RS
+.IP "\fBhdr=\fR\fIheader\fR"
+Used to specify encapsulation header type.
+.
+.IP "\fBprop(class=\fR\fIclass\fR\fB,type=\fR\fItype\fR\fB,val=\fR\fIvalue\fR\fB)\fR"
+Used to specify the initial value for the field in the encapsulation header.
+.RE
+.IP
+For example, \fBencap(hdr=ethernet)\fR will encapsulate the L3 packet with
+Ethernet header.
+.IP
+\fBencap(hdr=nsh,prop(class=nsh,type=md_type,val=1))\fR will encapsulate
+the packet with nsh header and nsh metadata type 1.
+.IP
+\fBencap(hdr=nsh,prop(class=nsh,type=md_type,val=2)\fR
+\fB,prop(class=nsh,type=tlv,val(0x1000,10,0x12345678)))\fR will encapsulate
+the packet with nsh header and nsh metadata type 2, and the nsh TLV with
+class 0x1000 and type 10 is set to 0x12345678.
+.IP
+\fBprop(...)\fR is just used to set some
+necessary fields for encapsulation header initialization. Other fields
+in the encapsulation header must be set by \fBset_field\fR action. New
+encapsulation header implementation must add new match fields and
+corresponding \fBset\fR action in order that \fBset_field\fR action can
+change the fields in the encapsulation header on demand.
+.IP
+\fBencap(hdr=nsh,prop(class=nsh,type=md_type,val=1)),\fR
+\fBset_field:0x1234->nsh_spi,set_field:0x11223344->nsh_c1\fR
+is an example to encapsulate nsh header and set nsh spi and c1.
+.IP
+This action was added in Open vSwitch 2.8.
+.
+.IP "\fBdecap(\fR[\fBpacket_type(ns=\fR\fInamespace\fR\fB,type=\fR\fItype\fR\fB)\fR]\fB)\fR"
+Decapsulates the outer packet header.
+.
+.RS
+.IP "\fBpacket_type(ns=\fR\fInamespace\fR\fB,type=\fR\fItype\fR\fB)\fR"
+It is optional and used to specify the outer header type of the
+decapsulated packet. \fInamespace\fR is 0 for Ethernet packet,
+1 for L3 packet, \fItype\fR\ is L3 protocol type, e.g.,
+0x894f for nsh, 0x0 for Ethernet.
+.RE
+.IP
+By default, \fBdecap()\fR will decapsulate the outer packet header
+according to the packet header type, if
+\fBpacket_type(ns=\fR\fInamespace\fR\fB,type=\fR\fItype\fR\fB)\fR
+is given, it will decapsulate the given packet header, it will fail
+if the actual outer packet header type is not of
+\fBpacket_type(ns=\fR\fInamespace\fR\fB,type=\fR\fItype\fR\fB)\fR.
+.IP
+This action was added in Open vSwitch 2.8.
 .RE
 .
 .PP
