[ovs-dev,RFC,2/2] Add encap_nsh & decap_nsh actions support in datapath

Submitted by Yang, Yi Y on April 20, 2017, 3:26 a.m.

Details

Message ID 1492658801-121522-3-git-send-email-yi.y.yang@intel.com
State New
Headers show

Commit Message

Yang, Yi Y April 20, 2017, 3:26 a.m.
Signed-off-by: Yi Yang <yi.y.yang@intel.com>
---
 datapath/linux/compat/include/linux/openvswitch.h |  23 ++++
 lib/automake.mk                                   |   2 +-
 lib/dpif-netdev.c                                 |   2 +
 lib/dpif.c                                        |   2 +
 lib/odp-execute.c                                 |  18 ++-
 lib/odp-util.c                                    | 154 ++++++++++++++++++++++
 lib/packets.c                                     |  48 +++++++
 lib/packets.h                                     |   3 +
 ofproto/ofproto-dpif-sflow.c                      |   2 +
 ofproto/ofproto-dpif-xlate.c                      |  97 +++++++++++++-
 10 files changed, 344 insertions(+), 7 deletions(-)

Patch hide | download patch | download mbox

diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h
index 5a64b38..03b10ff 100644
--- a/datapath/linux/compat/include/linux/openvswitch.h
+++ b/datapath/linux/compat/include/linux/openvswitch.h
@@ -773,6 +773,25 @@  struct ovs_action_endecap {
 	uint8_t  props[OVS_ENDECAP_MAX_PROPS_LEN];
 };
 
+#define OVS_ENCAP_NSH_MAX_MD_LEN 256
+/*
+ * struct ovs_action_encap_nsh - %OVS_ACTION_ATTR_ENCAP_NSH
+ * @flags: NSH header flags.
+ * @length: NSH header length/4.
+ * @md_type: NSH Metadata type.
+ * @next_proto: Inner packet type.
+ * @path_hdr: NSH service path and service index.
+ * @metadata: NSH metadata for MD type 1 or 2
+ */
+struct ovs_action_encap_nsh {
+	uint8_t flags;
+	uint8_t length;
+	uint8_t md_type;
+	uint8_t next_proto;
+	ovs_be32 path_hdr;
+	uint8_t metadata[OVS_ENCAP_NSH_MAX_MD_LEN];
+};
+
 /**
  * enum ovs_nat_attr - Attributes for %OVS_CT_ATTR_NAT.
  *
@@ -852,6 +871,8 @@  enum ovs_nat_attr {
  * as NSH header, Ethernet header, etc.
  * @OVS_ACTION_ATTR_DECAP: Generic decap action to remove generic header, such
  * as NSH header, Ethernet header, etc.
+ * @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
@@ -889,6 +910,8 @@  enum ovs_action_attr {
 	OVS_ACTION_ATTR_POP_ETH,      /* No argument. */
 	OVS_ACTION_ATTR_ENCAP,        /* struct ovs_action_endecap. */
 	OVS_ACTION_ATTR_DECAP,        /* struct ovs_action_endecap. */
+	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/lib/automake.mk b/lib/automake.mk
index 39977a6..26a51b6 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -149,7 +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-ed-props.c \
 	lib/ofp-errors.c \
 	lib/ofp-msgs.c \
 	lib/ofp-parse.c \
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index f5fd00c..e3b6862 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -5177,6 +5177,8 @@  dp_execute_cb(void *aux_, struct dp_packet_batch *packets_,
     case OVS_ACTION_ATTR_CLONE:
     case OVS_ACTION_ATTR_ENCAP:
     case OVS_ACTION_ATTR_DECAP:
+    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 fa0f617..3febf29 100644
--- a/lib/dpif.c
+++ b/lib/dpif.c
@@ -1226,6 +1226,8 @@  dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_,
     case OVS_ACTION_ATTR_CLONE:
     case OVS_ACTION_ATTR_ENCAP:
     case OVS_ACTION_ATTR_DECAP:
+    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/odp-execute.c b/lib/odp-execute.c
index 4df1f0c..af6407d 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -617,6 +617,8 @@  requires_datapath_assistance(const struct nlattr *a)
     case OVS_ACTION_ATTR_CLONE:
     case OVS_ACTION_ATTR_ENCAP:
     case OVS_ACTION_ATTR_DECAP:
+    case OVS_ACTION_ATTR_ENCAP_NSH:
+    case OVS_ACTION_ATTR_DECAP_NSH:
         return false;
 
     case OVS_ACTION_ATTR_UNSPEC:
@@ -774,11 +776,17 @@  odp_execute_actions(void *dp, struct dp_packet_batch *batch, bool steal,
             }
             break;
 
-        case OVS_ACTION_ATTR_ENCAP:
-            /* TODO */
+        case OVS_ACTION_ATTR_ENCAP_NSH: {
+            const struct ovs_action_encap_nsh *oaen = nl_attr_get(a);
+            DP_PACKET_BATCH_FOR_EACH (packet, batch) {
+                encap_nsh(packet, oaen);
+            }
             break;
-        case OVS_ACTION_ATTR_DECAP:
-            /* TODO */
+        }
+        case OVS_ACTION_ATTR_DECAP_NSH:
+            DP_PACKET_BATCH_FOR_EACH (packet, batch) {
+                decap_nsh(packet);
+            }
             break;
 
         case OVS_ACTION_ATTR_OUTPUT:
@@ -787,6 +795,8 @@  odp_execute_actions(void *dp, struct dp_packet_batch *batch, bool steal,
         case OVS_ACTION_ATTR_USERSPACE:
         case OVS_ACTION_ATTR_RECIRC:
         case OVS_ACTION_ATTR_CT:
+        case OVS_ACTION_ATTR_ENCAP:
+        case OVS_ACTION_ATTR_DECAP:
         case OVS_ACTION_ATTR_UNSPEC:
         case __OVS_ACTION_ATTR_MAX:
             OVS_NOT_REACHED();
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 6da7d78..7389e85 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -130,6 +130,8 @@  odp_action_len(uint16_t type)
     case OVS_ACTION_ATTR_CLONE: return ATTR_LEN_VARIABLE;
     case OVS_ACTION_ATTR_ENCAP: return ATTR_LEN_VARIABLE;
     case OVS_ACTION_ATTR_DECAP: 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:
@@ -247,6 +249,46 @@  format_odp_clone_action(struct ds *ds, const struct nlattr *attr)
     ds_put_format(ds, ")");
 }
 
+static void
+format_nsh_header(struct ds *ds, const struct nsh_hdr * nsh_header)
+{
+    uint8_t flags = (nsh_header->base.version << 6) &
+                    (nsh_header->base.oam_flag << 5) &
+                    (nsh_header->base.context_flag << 4) &
+                    nsh_header->base.reserved_flags1;
+    ds_put_format(ds, "flags=%d", flags);
+    ds_put_format(ds, ",mdtype=%d", nsh_header->base.md_type);
+    ds_put_format(ds, ",np=%d", nsh_header->base.next_proto);
+    ds_put_format(ds, ",spi=%d", ntohl(nsh_header->base.path_hdr << 8));
+    ds_put_format(ds, ",si=%d", nsh_header->base.svc_idx);
+
+    if (nsh_header->base.md_type == NSH_M_TYPE1) {
+        const struct nsh_md1_ctx *md1_ctx = nsh_md1_ctx(nsh_header);
+        ds_put_format(ds, ",c1=0x%08x", ntohl(md1_ctx->c1));
+        ds_put_format(ds, ",c2=0x%08x", ntohl(md1_ctx->c2));
+        ds_put_format(ds, ",c3=0x%08x", ntohl(md1_ctx->c3));
+        ds_put_format(ds, ",c4=0x%08x", ntohl(md1_ctx->c4));
+    } else if (nsh_header->base.md_type == NSH_M_TYPE2) {
+        /* TODO */
+    }
+}
+
+static void
+format_odp_encap_nsh_action(struct ds *ds, const struct nlattr *attr)
+{
+    if (nl_attr_type(attr) != OVS_ACTION_ATTR_ENCAP_NSH) {
+        return;
+    }
+    ds_put_cstr(ds, "encap_nsh(");
+
+    size_t len = nl_attr_get_size(attr);
+    if (len) {
+        const struct nsh_hdr * nsh_header  = (struct nsh_hdr *)nl_attr_get(attr);
+        format_nsh_header(ds, nsh_header);
+    }
+    ds_put_format(ds, ")");
+}
+
 static const char *
 slow_path_reason_to_string(uint32_t reason)
 {
@@ -919,6 +961,12 @@  format_odp_action(struct ds *ds, const struct nlattr *a)
     case OVS_ACTION_ATTR_DECAP:
         /* TODO */
         break;
+    case OVS_ACTION_ATTR_ENCAP_NSH:
+        format_odp_encap_nsh_action(ds, 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:
@@ -1637,6 +1685,94 @@  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;
+    struct nsh_hdr * nsh_header = (struct nsh_hdr *)&encap_nsh;
+    uint32_t spi;
+    uint8_t si;
+
+    if (!ovs_scan_len(s, &n, "encap_nsh(")) {
+        ret = -EINVAL;
+        goto out;
+    }
+
+    /* The default is NSH_M_TYPE1 */
+    nsh_header->base.version = 0;
+    nsh_header->base.oam_flag = 0;
+    nsh_header->base.context_flag = 0; //md_type=NSH_M_TYPE1
+    nsh_header->base.length = 6; //md_type=NSH_M_TYPE1
+
+    nsh_header->base.md_type = NSH_M_TYPE1;
+    nsh_header->base.next_proto = NSH_P_ETHERNET;
+    nsh_header->base.path_hdr = htonl(255);
+
+    struct nsh_md1_ctx * md1_ctx = nsh_md1_ctx(nsh_header);
+    md1_ctx->c1 = 0;
+    md1_ctx->c2 = 0;
+    md1_ctx->c3 = 0;
+    md1_ctx->c4 = 0;
+
+    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.md_type)) {
+            continue;
+        }
+        if (ovs_scan_len(s, &n, "np=%"SCNi8, &encap_nsh.next_proto)) {
+            continue;
+        }
+        if (ovs_scan_len(s, &n, "spi=%"SCNi32, &spi)) {
+            spi = htonl(spi);
+            encap_nsh.path_hdr = encap_nsh.path_hdr & ((spi >> 8) | 0xFF000000);
+            continue;
+        }
+        if (ovs_scan_len(s, &n, "si=%"SCNi8, &si)) {
+            encap_nsh.path_hdr = encap_nsh.path_hdr & ((si << 24) | 0x00FFFFFF);
+            continue;
+        }
+        if (encap_nsh.md_type == NSH_M_TYPE1) {
+            if (ovs_scan_len(s, &n, "c1=0x%"SCNx32, &md1_ctx->c1)) {
+                md1_ctx->c1 = htonl(md1_ctx->c1);
+                continue;
+            }
+            if (ovs_scan_len(s, &n, "c2=0x%"SCNx32, &md1_ctx->c2)) {
+                md1_ctx->c2 = htonl(md1_ctx->c2);
+                continue;
+            }
+            if (ovs_scan_len(s, &n, "c3=0x%"SCNx32, &md1_ctx->c3)) {
+                md1_ctx->c3 = htonl(md1_ctx->c3);
+                continue;
+            }
+            if (ovs_scan_len(s, &n, "c4=0x%"SCNx32, &md1_ctx->c4)) {
+                md1_ctx->c4 = htonl(md1_ctx->c4);
+                continue;
+            }
+        }
+        else if (encap_nsh.md_type == NSH_M_TYPE2) {
+            /* TODO */
+            continue;
+        }
+    }
+out:
+    if (ret < 0) {
+        return ret;
+    } else {
+        size_t size = nsh_header->base.length << 2;
+        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)
 {
@@ -1838,6 +1974,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;
 
diff --git a/lib/packets.c b/lib/packets.c
index 9467768..c235b26 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -226,6 +226,54 @@  eth_pop_vlan(struct dp_packet *packet)
     }
 }
 
+void
+encap_nsh(struct dp_packet *packet, const struct ovs_action_encap_nsh * oaen) {
+    struct nsh_hdr * nsh_header = (struct nsh_hdr *)oaen;
+    uint16_t length = nsh_header->base.length << 2;
+    uint8_t next_proto = NSH_P_ETHERNET;
+
+    switch(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;
+    }
+
+    nsh_header = (struct nsh_hdr *)dp_packet_push_uninit(packet, length);
+    memcpy(nsh_header, oaen, length);
+    nsh_header->base.next_proto = next_proto;
+    packet->packet_type = htonl(PT_NSH);
+}
+
+void
+decap_nsh(struct dp_packet *packet) {
+    if ((packet->l3_ofs == 0) && (packet->packet_type == htonl(PT_NSH))) {
+        struct nsh_hdr * nsh_header = (struct nsh_hdr *)dp_packet_l3(packet);
+        uint8_t next_proto = nsh_header->base.next_proto;
+        enum packet_type next_pt = PT_ETH;
+        switch(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;
+        }
+
+        size_t length = nsh_header->base.length << 2;
+        dp_packet_reset_packet(packet, length);
+        packet->packet_type = htonl(next_pt);
+    }
+}
+
 /* Push Ethernet header onto 'packet' assuming it is layer 3 */
 void
 push_eth(struct dp_packet *packet, const struct eth_addr *dst,
diff --git a/lib/packets.h b/lib/packets.h
index 78ea838..8dbf399 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -404,6 +404,9 @@  struct eth_header {
 });
 BUILD_ASSERT_DECL(ETH_HEADER_LEN == sizeof(struct eth_header));
 
+void encap_nsh(struct dp_packet *packet, const struct ovs_action_encap_nsh * encap_nsh);
+void decap_nsh(struct dp_packet *packet);
+
 void push_eth(struct dp_packet *packet, const struct eth_addr *dst,
               const struct eth_addr *src, ovs_be16 type);
 void pop_eth(struct dp_packet *packet);
diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
index 0d4e8f1..a59727c 100644
--- a/ofproto/ofproto-dpif-sflow.c
+++ b/ofproto/ofproto-dpif-sflow.c
@@ -1201,6 +1201,8 @@  dpif_sflow_read_actions(const struct flow *flow,
 	case OVS_ACTION_ATTR_CLONE:
 	case OVS_ACTION_ATTR_ENCAP:
 	case OVS_ACTION_ATTR_DECAP:
+	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 a74e1ac..fd717b0 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -4161,6 +4161,8 @@  xlate_fixup_actions(struct ofpbuf *b, const struct nlattr *actions,
         case OVS_ACTION_ATTR_POP_ETH:
         case OVS_ACTION_ATTR_ENCAP:
         case OVS_ACTION_ATTR_DECAP:
+        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;
@@ -5453,6 +5455,91 @@  compose_conntrack_action(struct xlate_ctx *ctx, struct ofpact_conntrack *ofc)
 }
 
 static void
+compose_encap_nsh_action(const struct ofpact_encap *ofe, struct ovs_action_encap_nsh * encap_nsh)
+{
+    char * ptr = (char *)ofe->props;
+    size_t ofs = 0;
+    struct nsh_hdr * nsh_header = (struct nsh_hdr *)encap_nsh;
+
+    /* The default is NSH_M_TYPE1 */
+    nsh_header->base.version = 0;
+    nsh_header->base.oam_flag = 0;
+    nsh_header->base.context_flag = 0; //md_type=NSH_M_TYPE1
+    nsh_header->base.length = 6; //md_type=NSH_M_TYPE1
+
+    nsh_header->base.md_type = NSH_M_TYPE1;
+    nsh_header->base.next_proto = NSH_P_ETHERNET;
+    nsh_header->base.path_hdr = htonl(255);
+
+    struct nsh_md1_ctx * md1_ctx = nsh_md1_ctx(nsh_header);
+    md1_ctx->c1 = 0;
+    md1_ctx->c2 = 0;
+    md1_ctx->c3 = 0;
+    md1_ctx->c4 = 0;
+
+    if (ofe->props_len == 0) {
+        return;
+    }
+
+    struct ofp_ed_prop_header * prop_ptr = (struct ofp_ed_prop_header *)ptr;
+    if ((prop_ptr->prop_class == OFPPPC_NSH) && (prop_ptr->type == OFPPPT_PROP_NSH_MDTYPE)) {
+        struct ofp_ed_prop_nsh_md_type * md_type_prop = (struct ofp_ed_prop_nsh_md_type *)prop_ptr;
+        nsh_header->base.md_type = md_type_prop->md_type;
+        switch(nsh_header->base.md_type) {
+        case NSH_M_TYPE1:
+            break;
+        case NSH_M_TYPE2:
+            nsh_header->base.context_flag = 1; //md_type=NSH_M_TYPE2
+            nsh_header->base.length = 2; //md_type=NSH_M_TYPE2
+            break;
+        }
+        ptr += sizeof(struct ofp_ed_prop_nsh_md_type);
+        ofs += sizeof(struct ofp_ed_prop_nsh_md_type);
+
+
+        /* Get TLVs */
+        struct nsh_md2_ctx * md2_ctx = nsh_md2_ctx(nsh_header);
+        while (ofs + sizeof(struct ofp_ed_prop_nsh_tlv) < ofe->props_len) {
+            struct ofp_ed_prop_nsh_tlv * tlv_prop = (struct ofp_ed_prop_nsh_tlv *)ptr;
+            md2_ctx->md_class = htons(tlv_prop->tlv_class);
+            md2_ctx->type = tlv_prop->tlv_type;
+            md2_ctx->length = tlv_prop->tlv_len;
+            memcpy(md2_ctx->md_value, tlv_prop->data, md2_ctx->length);
+            nsh_header->base.length += 1 + md2_ctx->length/4;
+            ptr += sizeof(struct ofp_ed_prop_nsh_tlv) + tlv_prop->tlv_len;
+            ofs += sizeof(struct ofp_ed_prop_nsh_tlv) + tlv_prop->tlv_len;
+            md2_ctx = (struct nsh_md2_ctx *)((char *)md2_ctx + 4 + md2_ctx->length);
+        }
+    } /* nsh encap */
+}
+
+static void
+compose_generic_encap_action(struct xlate_ctx *ctx, struct ofpact_encap *ofe)
+{
+    /* Ensure that any prior actions are applied before composing generic
+     * encap action. */
+    xlate_commit_actions(ctx);
+
+    /* encap_nsh */
+    if (PACKET_TYPE(ofe->new_pkt_type.ns, ofe->new_pkt_type.type) == PT_NSH) {
+        struct ovs_action_encap_nsh encap_nsh;
+        struct nsh_hdr * nsh_header = (struct nsh_hdr *)&encap_nsh;
+
+        compose_encap_nsh_action(ofe, &encap_nsh);
+        nl_msg_put_unspec(ctx->odp_actions, OVS_ACTION_ATTR_ENCAP_NSH, &encap_nsh, nsh_header->base.length << 2);
+    }
+}
+
+static void
+compose_generic_decap_action(struct xlate_ctx *ctx, struct ofpact_decap *ofd)
+{
+    /* decap_nsh */
+    if (PACKET_TYPE(ofd->new_pkt_type.ns, ofd->new_pkt_type.type) == PT_NSH) {
+        nl_msg_put_flag(ctx->odp_actions, OVS_ACTION_ATTR_DECAP_NSH);
+    }
+}
+
+static void
 recirc_for_mpls(const struct ofpact *a, struct xlate_ctx *ctx)
 {
     /* No need to recirculate if already exiting. */
@@ -5944,11 +6031,17 @@  do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             break;
 
         case OFPACT_ENCAP:
-            /* TODO */
+            compose_generic_encap_action(ctx, ofpact_get_ENCAP(a));
+            /* Recirculate for parse the encaped header */
+            ctx_trigger_freeze(ctx);
+            a = ofpact_next(a);
             break;
 
         case OFPACT_DECAP:
-            /* TODO */
+            compose_generic_decap_action(ctx, ofpact_get_DECAP(a));
+            /* Recirculate for parse inner header */
+            ctx_trigger_freeze(ctx);
+            a = ofpact_next(a);
             break;
 
         case OFPACT_CT: