[ovs-dev] [PATCH v2.0] Non-Datapath MPLS actions and matches

Simon Horman horms at verge.net.au
Wed Sep 26 07:13:41 UTC 2012


This patch provides an implementation of the non-datapath portions
of MPLS matches and actions.

This patch is based on top of Ben Pfaff's series,
"set-field action support"

Cc: Isaku Yamahata <yamahata at valinux.co.jp>
Cc: Ravi K <rkerur at gmail.com>
Signed-off-by: Simon Horman <horms at verge.net.au>

---

v2.0 [Simon Horman]
* Rebase to current master
  - First by Isaku Yamahata in July;
  - Then by me more recently.
* Remove datapath changes
  - This is intended to provide a first step for
    merging MPLS. The non-datapath changes seem
    largely uncontroversial while the datapath
    changes were incomplete with respect to
    agreement on the implementation for TTL actions
    and offloads.
* Correct dec_mpls_ttl to decerment ttl by 1 rather than 2
  - Fix by Isaku Yamahata
* Bug fixes to allow test suite to pass
* Make sparse clean
* Introduce encap_dl_type element to struct flow.
  This is used to store the dl_type of the encapsulated packet
  and is used by odp_flow_key_to_flow() and flow_compose() to
  allow eth_from_packet_or_flow() to be able to handle
  e.g. an MPLS encoded IPv4 packet.
* update packet.c:get_label_ttl_and_tc() to treat unknown eth type
  asn an error.
* Remove datapath changes

Previous Revisions by Ravi K

Todo:

Remove NX matches and actions? Are they necessary?

---
 include/linux/openvswitch.h     |    2 +
 include/openflow/nicira-ext.h   |   90 ++++++++++
 include/openflow/openflow-1.2.h |    2 +
 lib/dpif-netdev.c               |    2 +
 lib/flow.c                      |  128 ++++++++++++--
 lib/flow.h                      |   23 ++-
 lib/match.c                     |   69 +++++++-
 lib/match.h                     |    6 +
 lib/meta-flow.c                 |  124 ++++++++++++++
 lib/meta-flow.h                 |    9 +
 lib/nx-match.c                  |   22 ++-
 lib/odp-util.c                  |  179 +++++++++++++++++++-
 lib/ofp-actions.c               |  265 +++++++++++++++++++++++++++++
 lib/ofp-actions.h               |   56 ++++++
 lib/ofp-parse.c                 |   44 +++++
 lib/ofp-print.c                 |    4 +
 lib/ofp-util.c                  |   30 +++-
 lib/ofp-util.def                |   16 ++
 lib/ofpbuf.c                    |    8 +-
 lib/ofpbuf.h                    |    1 +
 lib/packets.c                   |  356 +++++++++++++++++++++++++++++++++++++++
 lib/packets.h                   |   88 ++++++++++
 ofproto/ofproto-dpif.c          |  232 ++++++++++++++++++++++++-
 tests/automake.mk               |    5 +
 tests/odp.at                    |   16 +-
 tests/ofp-print.at              |    6 +-
 tests/ofproto-dpif.at           |  183 +++++++++++++++++---
 tests/ofproto.at                |   12 +-
 tests/test-bundle.c             |    2 +-
 tests/test-mpls.c               |  288 +++++++++++++++++++++++++++++++
 tests/test-multipath.c          |    2 +-
 utilities/ovs-dpctl.c           |   18 +-
 utilities/ovs-ofctl.8.in        |   45 +++++
 33 files changed, 2259 insertions(+), 74 deletions(-)
 create mode 100644 tests/test-mpls.c

diff --git a/include/linux/openvswitch.h b/include/linux/openvswitch.h
index f5c9cca..fcb6895 100644
--- a/include/linux/openvswitch.h
+++ b/include/linux/openvswitch.h
@@ -278,6 +278,8 @@ enum ovs_key_attr {
 	OVS_KEY_ATTR_ICMPV6,    /* struct ovs_key_icmpv6 */
 	OVS_KEY_ATTR_ARP,       /* struct ovs_key_arp */
 	OVS_KEY_ATTR_ND,        /* struct ovs_key_nd */
+	OVS_KEY_ATTR_MPLS,      /* be32 MPLS Label Stack Entry */
+	OVS_KEY_ATTR_INNER_MPLS,/* be32 Inner MPLS Label Stack Entry */
 	OVS_KEY_ATTR_TUN_ID = 63, /* be64 tunnel ID */
 	__OVS_KEY_ATTR_MAX
 };
diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
index 75bf6db..80a7a8b 100644
--- a/include/openflow/nicira-ext.h
+++ b/include/openflow/nicira-ext.h
@@ -304,6 +304,14 @@ enum nx_action_subtype {
     NXAST_FIN_TIMEOUT,          /* struct nx_action_fin_timeout */
     NXAST_CONTROLLER,           /* struct nx_action_controller */
     NXAST_DEC_TTL_CNT_IDS,      /* struct nx_action_cnt_ids */
+    NXAST_COPY_TTL_OUT,         /* struct nx_action_header */
+    NXAST_COPY_TTL_IN,          /* struct nx_action_header */
+    NXAST_SET_MPLS_LABEL,       /* struct nx_action_mpls_label */
+    NXAST_SET_MPLS_TC,          /* struct nx_action_mpls_tc */
+    NXAST_SET_MPLS_TTL,         /* struct nx_action_mpls_ttl */
+    NXAST_DEC_MPLS_TTL,         /* struct nx_action_header */
+    NXAST_PUSH_MPLS,            /* struct nx_action_push_mpls */
+    NXAST_POP_MPLS,             /* struct nx_action_pop_mpls */
 };
 
 /* Header for Nicira-defined actions. */
@@ -1757,6 +1765,33 @@ OFP_ASSERT(sizeof(struct nx_action_output_reg) == 24);
 #define NXM_NX_COOKIE     NXM_HEADER  (0x0001, 30, 8)
 #define NXM_NX_COOKIE_W   NXM_HEADER_W(0x0001, 30, 8)
 
+/* The mpls_label in MPLS shim header.
+ *
+ * Prereqs: NXM_OF_ETH_TYPE must be either 0x8847 or 0x8848.
+ *
+ * Format: 32-bit integer, lower 20 bits
+ *
+ * Masking: Not maskable. */
+#define NXM_NX_MPLS_LABEL      NXM_HEADER  (0x0001, 31, 4)
+
+/* The mpls_tc in MPLS shim header.
+ *
+ * Prereqs: NXM_OF_ETH_TYPE must be either 0x8847 or 0x8848.
+ *
+ * Format: 8-bit integer, lower 3 bits
+ *
+ * Masking: Not maskable. */
+#define NXM_NX_MPLS_TC      NXM_HEADER  (0x0001, 32, 1)
+
+/* The mpls_stack in MPLS shim header.
+ *
+ * Prereqs: NXM_OF_ETH_TYPE must be either 0x8847 or 0x8848.
+ *
+ * Format: 8-bit integer, lower 1 bit
+ *
+ * Masking: Not maskable. */
+#define NXM_NX_MPLS_STACK   NXM_HEADER  (0x0001, 33, 1)
+
 /* ## --------------------- ## */
 /* ## Requests and replies. ## */
 /* ## --------------------- ## */
@@ -2192,4 +2227,59 @@ struct nx_flow_monitor_cancel {
 };
 OFP_ASSERT(sizeof(struct nx_flow_monitor_cancel) == 4);
 
+/* Action structure for NXAST_SET_MPLS_LABEL. */
+struct nx_action_mpls_label {
+    ovs_be16 type;                  /* OFPAT_SET_MPLS_LABEL. */
+    ovs_be16 len;                   /* Length is 8. */
+    ovs_be32 vendor;                /* NX_VENDOR_ID. */
+    ovs_be16 subtype;               /* NXAST_SET_MPLS_LABEL. */
+    uint8_t pad[2];
+    ovs_be32 mpls_label;            /* MPLS label in low 20 bits. */
+};
+OFP_ASSERT(sizeof(struct nx_action_mpls_label) == 16);
+
+/* Action structure for NXAST_SET_MPLS_TC. */
+struct nx_action_mpls_tc {
+    ovs_be16 type;                  /* OFPAT_SET_MPLS_TC. */
+    ovs_be16 len;                   /* Length is 8. */
+    ovs_be32 vendor;                /* NX_VENDOR_ID. */
+    ovs_be16 subtype;               /* NXAST_SET_MPLS_TC. */
+    uint8_t  mpls_tc;               /* MPLS TC */
+    uint8_t  pad[5];
+};
+OFP_ASSERT(sizeof(struct nx_action_mpls_tc) == 16);
+
+/* Action structure for NXAST_SET_MPLS_TTL. */
+struct nx_action_mpls_ttl {
+    ovs_be16 type;                  /* OFPAT_SET_MPLS_TTL. */
+    ovs_be16 len;                   /* Length is 8. */
+    ovs_be32 vendor;                /* NX_VENDOR_ID. */
+    ovs_be16 subtype;               /* NXAST_SET_MPLS_TTL. */
+    uint8_t  mpls_ttl;              /* MPLS TTL */
+    uint8_t  pad[5];
+};
+OFP_ASSERT(sizeof(struct nx_action_mpls_ttl) == 16);
+
+/* Action structure for NXAST_PUSH_VLAN/MPLS. */
+struct nx_action_push {
+    ovs_be16 type;                  /* OFPAT_PUSH_VLAN/MPLS. */
+    ovs_be16 len;                   /* Length is 8. */
+    ovs_be32 vendor;                /* NX_VENDOR_ID. */
+    ovs_be16 subtype;               /* NXAST_PUSH_MPLS. */
+    ovs_be16 ethertype;             /* Ethertype */
+    uint8_t  pad[4];
+};
+OFP_ASSERT(sizeof(struct nx_action_push) == 16);
+
+/* Action structure for NXAST_POP_MPLS. */
+struct nx_action_pop_mpls {
+    ovs_be16 type;                  /* OFPAT_POP_MPLS. */
+    ovs_be16 len;                   /* Length is 8. */
+    ovs_be32 vendor;                /* NX_VENDOR_ID. */
+    ovs_be16 subtype;               /* NXAST_POP_MPLS. */
+    ovs_be16 ethertype;             /* Ethertype */
+    uint8_t  pad[4];
+};
+OFP_ASSERT(sizeof(struct nx_action_pop_mpls) == 16);
+
 #endif /* openflow/nicira-ext.h */
diff --git a/include/openflow/openflow-1.2.h b/include/openflow/openflow-1.2.h
index 1c3f017..771e5bb 100644
--- a/include/openflow/openflow-1.2.h
+++ b/include/openflow/openflow-1.2.h
@@ -106,6 +106,7 @@ enum oxm12_ofb_match_fields {
     OFPXMT12_OFB_IPV6_ND_TLL,    /* Target link-layer for ND. */
     OFPXMT12_OFB_MPLS_LABEL,     /* MPLS label. */
     OFPXMT12_OFB_MPLS_TC,        /* MPLS TC. */
+    OFPXMT12_OFB_MPLS_STACK,     /* MPLS stack. */
 
     /* End Marker */
     OFPXMT12_OFB_MAX,
@@ -172,6 +173,7 @@ enum oxm12_ofb_match_fields {
 #define OXM_OF_IPV6_ND_TLL    OXM_HEADER   (OFPXMT12_OFB_IPV6_ND_TLL, 6)
 #define OXM_OF_MPLS_LABEL     OXM_HEADER   (OFPXMT12_OFB_MPLS_LABEL, 4)
 #define OXM_OF_MPLS_TC        OXM_HEADER   (OFPXMT12_OFB_MPLS_TC, 1)
+#define OXM_OF_MPLS_STACK     OXM_HEADER   (OFPXMT12_OFB_MPLS_STACK, 1)
 
 /* The VLAN id is 12-bits, so we can use the entire 16 bits to indicate
  * special conditions.
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index 144b6b6..778031e 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -1209,6 +1209,8 @@ execute_set_action(struct ofpbuf *packet, const struct nlattr *a)
      case OVS_KEY_ATTR_ETHERTYPE:
      case OVS_KEY_ATTR_IN_PORT:
      case OVS_KEY_ATTR_VLAN:
+     case OVS_KEY_ATTR_MPLS:
+     case OVS_KEY_ATTR_INNER_MPLS:
      case OVS_KEY_ATTR_ICMP:
      case OVS_KEY_ATTR_ICMPV6:
      case OVS_KEY_ATTR_ARP:
diff --git a/lib/flow.c b/lib/flow.c
index e517a03..9897a61 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -93,6 +93,33 @@ pull_icmpv6(struct ofpbuf *packet)
 }
 
 static void
+parse_remaining_mpls(struct ofpbuf *b, struct flow *flow)
+{
+    /* Proceed with parsing remaining MPLS headers. */
+    struct mpls_hdr *mh = ofpbuf_pull(b, sizeof *mh);
+    while (!(mh->mpls_lse & htonl(MPLS_STACK_MASK))) {
+        if (flow->inner_mpls_lse == htonl(0)) {
+            flow->inner_mpls_lse = mh->mpls_lse;
+        }
+        mh = ofpbuf_pull(b, sizeof *mh);
+    }
+    if (flow->inner_mpls_lse == htonl(0)) {
+        flow->inner_mpls_lse = mh->mpls_lse;
+    }
+}
+
+static void
+parse_mpls(struct ofpbuf *b, struct flow *flow)
+{
+    /* Make sure there is some data following MPLS header
+       before proceeding with parsing MPLS headers. */
+    if (b->size >= sizeof(struct mpls_hdr) + sizeof(ovs_be16)) {
+        struct mpls_hdr *mh = ofpbuf_pull(b, sizeof *mh);
+        flow->mpls_lse = mh->mpls_lse;
+    }
+}
+
+static void
 parse_vlan(struct ofpbuf *b, struct flow *flow)
 {
     struct qtag_prefix {
@@ -323,6 +350,8 @@ invalid:
  *
  *    - packet->l2 to the start of the Ethernet header.
  *
+ *    - packet->l2_5 to the start of the MPLS shim header.
+ *
  *    - packet->l3 to just past the Ethernet header, or just past the
  *      vlan_header if one is present, to the first byte of the payload of the
  *      Ethernet frame.
@@ -347,10 +376,11 @@ flow_extract(struct ofpbuf *packet, uint32_t skb_priority, ovs_be64 tun_id,
     flow->in_port = ofp_in_port;
     flow->skb_priority = skb_priority;
 
-    packet->l2 = b.data;
-    packet->l3 = NULL;
-    packet->l4 = NULL;
-    packet->l7 = NULL;
+    packet->l2   = b.data;
+    packet->l2_5 = NULL;
+    packet->l3   = NULL;
+    packet->l4   = NULL;
+    packet->l7   = NULL;
 
     if (b.size < sizeof *eth) {
         return;
@@ -368,6 +398,25 @@ flow_extract(struct ofpbuf *packet, uint32_t skb_priority, ovs_be64 tun_id,
     }
     flow->dl_type = parse_ethertype(&b);
 
+    /* Parse mpls, copy l3 ttl. */
+    if (flow->dl_type == htons(ETH_TYPE_MPLS) ||
+        flow->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
+        struct ip_header *ih = b.data;
+        struct ip6_hdr   *ih6 = b.data;
+        packet->l2_5 = b.data;
+        parse_mpls(&b, flow);
+        if (!(flow->mpls_lse & htonl(MPLS_STACK_MASK))) {
+            parse_remaining_mpls(&b, flow);
+        }
+        if (ih) {
+            if (IP_VER(ih->ip_ihl_ver) == IP_VERSION) {
+                flow->nw_ttl = ih->ip_ttl;
+            } else if (IP6_VER(ih6->ip6_vfc) == IP6_VERSION) {
+                flow->nw_ttl = ih6->ip6_hlim;
+            }
+        }
+    }
+
     /* Network layer. */
     packet->l3 = b.data;
     if (flow->dl_type == htons(ETH_TYPE_IP)) {
@@ -458,7 +507,7 @@ flow_zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards)
 void
 flow_get_metadata(const struct flow *flow, struct flow_metadata *fmd)
 {
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 17);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 18);
 
     fmd->tun_id = flow->tun_id;
     fmd->metadata = flow->metadata;
@@ -500,6 +549,18 @@ flow_format(struct ds *ds, const struct flow *flow)
                   ETH_ADDR_ARGS(flow->dl_dst),
                   ntohs(flow->dl_type));
 
+    ds_put_format(ds, ",mpls(");
+    if (flow->mpls_lse) {
+        ds_put_format(ds, "label:%"PRIu32",tc:%d,ttl:%d,bos:%d",
+                      mpls_lse_to_label(flow->mpls_lse),
+                      mpls_lse_to_tc(flow->mpls_lse),
+                      mpls_lse_to_ttl(flow->mpls_lse),
+                      mpls_lse_to_stack(flow->mpls_lse));
+    } else {
+        ds_put_char(ds, '0');
+    }
+    ds_put_char(ds, ')');
+
     if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
         ds_put_format(ds, " label:%#"PRIx32" proto:%"PRIu8" tos:%#"PRIx8
                           " ttl:%"PRIu8" ipv6(",
@@ -556,7 +617,7 @@ void
 flow_wildcards_init_exact(struct flow_wildcards *wc)
 {
     memset(&wc->masks, 0xff, sizeof wc->masks);
-    memset(wc->masks.zeros, 0, sizeof wc->masks.zeros);
+    flow_zero_pad(&wc->masks);
 }
 
 /* Returns true if 'wc' matches every packet, false if 'wc' fixes any bits or
@@ -788,6 +849,39 @@ flow_set_vlan_pcp(struct flow *flow, uint8_t pcp)
     flow->vlan_tci |= htons((pcp << VLAN_PCP_SHIFT) | VLAN_CFI);
 }
 
+/* Sets the MPLS Label that 'flow' matches to 'label', which is interpreted
+ * as an OpenFlow 1.1 "mpls_label" value. */
+void
+flow_set_mpls_label(struct flow *flow, ovs_be32 label)
+{
+    if (label == htonl(0)) {
+        flow->mpls_lse = htonl(0);
+    } else {
+        flow->mpls_lse &= ~htonl(MPLS_LABEL_MASK);
+        flow->mpls_lse |=
+            htonl((ntohl(label) << MPLS_LABEL_SHIFT) & MPLS_LABEL_MASK);
+    }
+}
+
+/* Sets the MPLS TC that 'flow' matches to 'tc', which should be in the
+ * range 0...7. */
+void
+flow_set_mpls_tc(struct flow *flow, uint8_t tc)
+{
+    tc &= 0x07;
+    flow->mpls_lse &= ~htonl(MPLS_TC_MASK);
+    flow->mpls_lse |= htonl(tc << MPLS_TC_SHIFT);
+}
+
+/* Sets the MPLS STACK bit that 'flow' matches to which should be 0 or 1. */
+void
+flow_set_mpls_stack(struct flow *flow, uint8_t stack)
+{
+    stack &= 0x01;
+    flow->mpls_lse &= ~htonl(MPLS_STACK_MASK);
+    flow->mpls_lse |= htonl(stack << MPLS_STACK_SHIFT);
+}
+
 /* Puts into 'b' a packet that flow_extract() would parse as having the given
  * 'flow'.
  *
@@ -797,7 +891,12 @@ flow_set_vlan_pcp(struct flow *flow, uint8_t pcp)
 void
 flow_compose(struct ofpbuf *b, const struct flow *flow)
 {
-    eth_compose(b, flow->dl_dst, flow->dl_src, ntohs(flow->dl_type), 0);
+    ovs_be16 inner_dl_type;
+
+    inner_dl_type = flow->encap_dl_type == htons(0)
+        ?  flow->dl_type
+        : flow->encap_dl_type;
+    eth_compose(b, flow->dl_dst, flow->dl_src, ntohs(inner_dl_type), 0);
     if (flow->dl_type == htons(FLOW_DL_TYPE_NONE)) {
         struct eth_header *eth = b->l2;
         eth->eth_type = htons(b->size);
@@ -808,7 +907,8 @@ flow_compose(struct ofpbuf *b, const struct flow *flow)
         eth_push_vlan(b, flow->vlan_tci);
     }
 
-    if (flow->dl_type == htons(ETH_TYPE_IP)) {
+    if (flow->dl_type == htons(ETH_TYPE_IP) ||
+        flow->encap_dl_type == htons(ETH_TYPE_IP)) {
         struct ip_header *ip;
 
         b->l3 = ip = ofpbuf_put_zeros(b, sizeof *ip);
@@ -853,9 +953,11 @@ flow_compose(struct ofpbuf *b, const struct flow *flow)
         ip->ip_tot_len = htons((uint8_t *) b->data + b->size
                                - (uint8_t *) b->l3);
         ip->ip_csum = csum(ip, sizeof *ip);
-    } else if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
+    } else if (flow->dl_type == htons(ETH_TYPE_IPV6) ||
+               flow->encap_dl_type == htons(ETH_TYPE_IPV6)) {
         /* XXX */
-    } else if (flow->dl_type == htons(ETH_TYPE_ARP)) {
+    } else if (flow->dl_type == htons(ETH_TYPE_ARP) ||
+               flow->encap_dl_type == htons(ETH_TYPE_ARP)) {
         struct arp_eth_header *arp;
 
         b->l3 = arp = ofpbuf_put_zeros(b, sizeof *arp);
@@ -873,6 +975,12 @@ flow_compose(struct ofpbuf *b, const struct flow *flow)
             memcpy(arp->ar_tha, flow->arp_tha, ETH_ADDR_LEN);
         }
     }
+
+    if (flow->dl_type == htons(ETH_TYPE_MPLS) ||
+        flow->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
+        push_mpls(b, flow->dl_type);
+        set_mpls_lse(b, flow->mpls_lse);
+    }
 }
 
 /* Compressed flow. */
diff --git a/lib/flow.h b/lib/flow.h
index fc62222..1117bd8 100644
--- a/lib/flow.h
+++ b/lib/flow.h
@@ -36,7 +36,7 @@ struct ofpbuf;
 /* This sequence number should be incremented whenever anything involving flows
  * or the wildcarding of flows changes.  This will cause build assertion
  * failures in places which likely need to be updated. */
-#define FLOW_WC_SEQ 17
+#define FLOW_WC_SEQ 18
 
 #define FLOW_N_REGS 8
 BUILD_ASSERT_DECL(FLOW_N_REGS <= NXM_NX_MAX_REGS);
@@ -53,6 +53,8 @@ BUILD_ASSERT_DECL(FLOW_N_REGS <= NXM_NX_MAX_REGS);
 BUILD_ASSERT_DECL(FLOW_NW_FRAG_ANY == NX_IP_FRAG_ANY);
 BUILD_ASSERT_DECL(FLOW_NW_FRAG_LATER == NX_IP_FRAG_LATER);
 
+#define FLOW_PAD_LEN 0
+
 struct flow {
     ovs_be64 tun_id;            /* Encapsulating tunnel ID. */
     ovs_be64 metadata;          /* OpenFlow Metadata. */
@@ -64,9 +66,12 @@ struct flow {
     ovs_be32 nw_src;            /* IPv4 source address. */
     ovs_be32 nw_dst;            /* IPv4 destination address. */
     ovs_be32 ipv6_label;        /* IPv6 flow label. */
+    ovs_be32 mpls_lse;          /* MPLS label stack entry. */
+    ovs_be32 inner_mpls_lse;    /* Inner MPLS label stack entry. */
     uint16_t in_port;           /* OpenFlow port number of input port. */
     ovs_be16 vlan_tci;          /* If 802.1Q, TCI | VLAN_CFI; otherwise 0. */
     ovs_be16 dl_type;           /* Ethernet frame type. */
+    ovs_be16 encap_dl_type;     /* MPLS encapsulated Ethernet frame type */
     ovs_be16 tp_src;            /* TCP/UDP source port. */
     ovs_be16 tp_dst;            /* TCP/UDP destination port. */
     uint8_t dl_src[6];          /* Ethernet source address. */
@@ -77,14 +82,14 @@ struct flow {
     uint8_t arp_tha[6];         /* ARP/ND target hardware address. */
     uint8_t nw_ttl;             /* IP TTL/Hop Limit. */
     uint8_t nw_frag;            /* FLOW_FRAG_* flags. */
-    uint8_t zeros[2];           /* Must be zero. */
+    uint8_t zeros[FLOW_PAD_LEN]; /* Must be zero. */
 };
 BUILD_ASSERT_DECL(sizeof(struct flow) % 8 == 0);
 
 #define FLOW_U32S (sizeof(struct flow) / 4)
 
 /* Remember to update FLOW_WC_SEQ when changing 'struct flow'. */
-BUILD_ASSERT_DECL(sizeof(struct flow) == 152 && FLOW_WC_SEQ == 17);
+BUILD_ASSERT_DECL(sizeof(struct flow) == 160 && FLOW_WC_SEQ == 18);
 
 /* Represents the metadata fields of struct flow. */
 struct flow_metadata {
@@ -94,6 +99,14 @@ struct flow_metadata {
     uint16_t in_port;                /* OpenFlow port or zero. */
 };
 
+#if (FLOW_PAD_LEN > 0)
+static inline void flow_zero_pad(struct flow *flow)
+{
+    memset(flow->zeros, 0, sizeof flow->zeros);
+}
+#else
+static inline void flow_zero_pad(struct flow *flow OVS_UNUSED) {;}
+#endif
 void flow_extract(struct ofpbuf *, uint32_t priority, ovs_be64 tun_id,
                   uint16_t in_port, struct flow *);
 void flow_zero_wildcards(struct flow *, const struct flow_wildcards *);
@@ -110,6 +123,10 @@ void flow_set_dl_vlan(struct flow *, ovs_be16 vid);
 void flow_set_vlan_vid(struct flow *, ovs_be16 vid);
 void flow_set_vlan_pcp(struct flow *, uint8_t pcp);
 
+void flow_set_mpls_label(struct flow *flow, ovs_be32 label);
+void flow_set_mpls_tc(struct flow *flow, uint8_t tc);
+void flow_set_mpls_stack(struct flow *flow, uint8_t stack);
+
 void flow_compose(struct ofpbuf *, const struct flow *);
 
 static inline int
diff --git a/lib/match.c b/lib/match.c
index 69129d4..ec883d8 100644
--- a/lib/match.c
+++ b/lib/match.c
@@ -280,6 +280,57 @@ match_set_dl_vlan_pcp(struct match *match, uint8_t dl_vlan_pcp)
     match->wc.masks.vlan_tci |= htons(VLAN_CFI | VLAN_PCP_MASK);
 }
 
+/* Modifies 'match' so that the MPLS label is wildcarded. */
+void
+match_set_any_mpls_label(struct match *match)
+{
+    match->wc.masks.mpls_lse |= ~htonl(MPLS_LABEL_MASK);
+    flow_set_mpls_label(&match->flow, htonl(0));
+}
+
+/* Modifies 'match' so that it matches only packets with an MPLS header whose
+ * label equals the low 20 bits of 'mpls_label'. */
+void
+match_set_mpls_label(struct match *match, ovs_be32 mpls_label)
+{
+    match->wc.masks.mpls_lse |= htonl(MPLS_LABEL_MASK);
+    flow_set_mpls_label(&match->flow, mpls_label);
+}
+
+/* Modifies 'match' so that the MPLS TC is wildcarded. */
+void
+match_set_any_mpls_tc(struct match *match)
+{
+    match->wc.masks.mpls_lse |= ~htonl(MPLS_TC_MASK);
+    flow_set_mpls_tc(&match->flow, 0);
+}
+
+/* Modifies 'match' so that it matches only packets with an MPLS header whose
+ * Traffic Class equals the low 3 bits of 'mpls_tc'. */
+void
+match_set_mpls_tc(struct match *match, uint8_t mpls_tc)
+{
+    match->wc.masks.mpls_lse |= htonl(MPLS_TC_MASK);
+    flow_set_mpls_tc(&match->flow, mpls_tc);
+}
+
+/* Modifies 'match' so that the MPLS stack flag is wildcarded. */
+void
+match_set_any_mpls_stack(struct match *match)
+{
+    match->wc.masks.mpls_lse |= ~htonl(MPLS_STACK_MASK);
+    flow_set_mpls_stack(&match->flow, 0);
+}
+
+/* Modifies 'match' so that it matches only packets with an MPLS header whose
+ * Stack Flag equals the lower bit of 'mpls_stack' */
+void
+match_set_mpls_stack(struct match *match, uint8_t mpls_stack)
+{
+    match->wc.masks.mpls_lse |= htonl(MPLS_STACK_MASK);
+    flow_set_mpls_stack(&match->flow, mpls_stack);
+}
+
 void
 match_set_tp_src(struct match *match, ovs_be16 tp_src)
 {
@@ -567,7 +618,7 @@ match_format(const struct match *match, struct ds *s, unsigned int priority)
 
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 17);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 18);
 
     if (priority != OFP_DEFAULT_PRIORITY) {
         ds_put_format(s, "priority=%u,", priority);
@@ -609,6 +660,10 @@ match_format(const struct match *match, struct ds *s, unsigned int priority)
             }
         } else if (f->dl_type == htons(ETH_TYPE_ARP)) {
             ds_put_cstr(s, "arp,");
+        } else if (f->dl_type == htons(ETH_TYPE_MPLS)) {
+            ds_put_cstr(s, "mpls,");
+        } else if (f->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
+            ds_put_cstr(s, "mplsm,");
         } else {
             skip_type = false;
         }
@@ -717,6 +772,18 @@ match_format(const struct match *match, struct ds *s, unsigned int priority)
     if (wc->masks.nw_ttl) {
         ds_put_format(s, "nw_ttl=%"PRIu8",", f->nw_ttl);
     }
+    if (wc->masks.mpls_lse & htonl(MPLS_LABEL_MASK)) {
+        ds_put_format(s, "mpls_label=%"PRIu32",",
+                 mpls_lse_to_label(f->mpls_lse));
+    }
+    if (wc->masks.mpls_lse & htonl(MPLS_TC_MASK)) {
+        ds_put_format(s, "mpls_tc=%"PRIu8",",
+                 mpls_lse_to_tc(f->mpls_lse));
+    }
+    if (wc->masks.mpls_lse & htonl(MPLS_STACK_MASK)) {
+        ds_put_format(s, "mpls_stack=%"PRIu8",",
+                 mpls_lse_to_stack(f->mpls_lse));
+    }
     switch (wc->masks.nw_frag) {
     case FLOW_NW_FRAG_ANY | FLOW_NW_FRAG_LATER:
         ds_put_format(s, "nw_frag=%s,",
diff --git a/lib/match.h b/lib/match.h
index 2d05819..f7cc033 100644
--- a/lib/match.h
+++ b/lib/match.h
@@ -65,6 +65,12 @@ void match_set_vlan_vid(struct match *, ovs_be16);
 void match_set_vlan_vid_masked(struct match *, ovs_be16 vid, ovs_be16 mask);
 void match_set_any_pcp(struct match *);
 void match_set_dl_vlan_pcp(struct match *, uint8_t);
+void match_set_any_mpls_label(struct match *);
+void match_set_mpls_label(struct match *, ovs_be32);
+void match_set_any_mpls_tc(struct match *);
+void match_set_mpls_tc(struct match *, uint8_t);
+void match_set_any_mpls_stack(struct match *);
+void match_set_mpls_stack(struct match *, uint8_t);
 void match_set_tp_src(struct match *, ovs_be16);
 void match_set_tp_src_masked(struct match *, ovs_be16 port, ovs_be16 mask);
 void match_set_tp_dst(struct match *, ovs_be16);
diff --git a/lib/meta-flow.c b/lib/meta-flow.c
index 38c9a27..504967c 100644
--- a/lib/meta-flow.c
+++ b/lib/meta-flow.c
@@ -194,6 +194,38 @@ static const struct mf_field mf_fields[MFF_N_IDS] = {
         OXM_OF_VLAN_PCP, "OXM_OF_VLAN_PCP",
     },
 
+    /* ## ---- ## */
+    /* ## L2.5 ## */
+    /* ## ---- ## */
+    {
+        MFF_MPLS_LABEL, "mpls_label", NULL,
+        4, 20,
+        MFM_NONE,
+        MFS_DECIMAL,
+        MFP_MPLS,
+        true,
+        NXM_NX_MPLS_LABEL, "NXM_NX_MPLS_LABEL",
+        0, NULL,
+    }, {
+        MFF_MPLS_TC, "mpls_tc", NULL,
+        1, 3,
+        MFM_NONE,
+        MFS_DECIMAL,
+        MFP_MPLS,
+        true,
+        NXM_NX_MPLS_TC, "NXM_NX_MPLS_TC",
+        0, NULL,
+    }, {
+        MFF_MPLS_STACK, "mpls_stack", NULL,
+        1, 1,
+        MFM_NONE,
+        MFS_DECIMAL,
+        MFP_MPLS,
+        true,
+        NXM_NX_MPLS_STACK, "NXM_NX_MPLS_STACK",
+        0, NULL,
+    },
+
     /* ## -- ## */
     /* ## L3 ## */
     /* ## -- ## */
@@ -607,6 +639,13 @@ mf_is_all_wild(const struct mf_field *mf, const struct flow_wildcards *wc)
     case MFF_VLAN_PCP:
         return !(wc->masks.vlan_tci & htons(VLAN_PCP_MASK));
 
+    case MFF_MPLS_LABEL:
+        return !(wc->masks.mpls_lse & htonl(MPLS_LABEL_MASK));
+    case MFF_MPLS_TC:
+        return !(wc->masks.mpls_lse & htonl(MPLS_TC_MASK));
+    case MFF_MPLS_STACK:
+        return !(wc->masks.mpls_lse & htonl(MPLS_STACK_MASK));
+
     case MFF_IPV4_SRC:
         return !wc->masks.nw_src;
     case MFF_IPV4_DST:
@@ -707,6 +746,16 @@ mf_get_mask(const struct mf_field *mf, const struct flow_wildcards *wc,
         mask->u8 = vlan_tci_to_pcp(wc->masks.vlan_tci);
         break;
 
+    case MFF_MPLS_LABEL:
+        mask->be32 = htonl(mpls_lse_to_label(wc->masks.mpls_lse));
+        break;
+    case MFF_MPLS_TC:
+        mask->u8 = mpls_lse_to_tc(wc->masks.mpls_lse);
+        break;
+    case MFF_MPLS_STACK:
+        mask->u8 = mpls_lse_to_stack(wc->masks.mpls_lse);
+        break;
+
     case MFF_IPV4_SRC:
         mask->be32 = wc->masks.nw_src;
         break;
@@ -841,6 +890,9 @@ mf_are_prereqs_ok(const struct mf_field *mf, const struct flow *flow)
         return flow->dl_type == htons(ETH_TYPE_IPV6);
     case MFP_VLAN_VID:
         return (flow->vlan_tci & htons(VLAN_CFI)) != 0;
+    case MFP_MPLS:
+        return (flow->dl_type == htons(ETH_TYPE_MPLS) ||
+                flow->dl_type == htons(ETH_TYPE_MPLS_MCAST));
     case MFP_IP_ANY:
         return is_ip_any(flow);
 
@@ -938,6 +990,15 @@ mf_is_value_valid(const struct mf_field *mf, const union mf_value *value)
     case MFF_IPV6_LABEL:
         return !(value->be32 & ~htonl(IPV6_LABEL_MASK));
 
+    case MFF_MPLS_LABEL:
+        return !(value->be32 & ~htonl(MPLS_LABEL_MASK >> MPLS_LABEL_SHIFT));
+
+    case MFF_MPLS_TC:
+        return !(value->u8 & ~7);
+
+    case MFF_MPLS_STACK:
+        return !(value->u8 & ~1);
+
     case MFF_N_IDS:
     default:
         NOT_REACHED();
@@ -994,6 +1055,18 @@ mf_get_value(const struct mf_field *mf, const struct flow *flow,
         value->u8 = vlan_tci_to_pcp(flow->vlan_tci);
         break;
 
+    case MFF_MPLS_LABEL:
+        value->be32 = htonl(mpls_lse_to_label(flow->mpls_lse));
+        break;
+
+    case MFF_MPLS_TC:
+        value->u8 = mpls_lse_to_tc(flow->mpls_lse);
+        break;
+
+    case MFF_MPLS_STACK:
+        value->u8 = mpls_lse_to_stack(flow->mpls_lse);
+        break;
+
     case MFF_IPV4_SRC:
         value->be32 = flow->nw_src;
         break;
@@ -1137,6 +1210,18 @@ mf_set_value(const struct mf_field *mf,
         match_set_dl_vlan_pcp(match, value->u8);
         break;
 
+    case MFF_MPLS_TC:
+        match_set_mpls_tc(match, value->u8);
+        break;
+
+    case MFF_MPLS_STACK:
+        match_set_mpls_stack(match, value->u8);
+        break;
+
+    case MFF_MPLS_LABEL:
+        match_set_mpls_label(match, value->be32);
+        break;
+
     case MFF_IPV4_SRC:
         match_set_nw_src(match, value->be32);
         break;
@@ -1280,6 +1365,18 @@ mf_set_flow_value(const struct mf_field *mf,
         flow_set_vlan_pcp(flow, value->u8);
         break;
 
+    case MFF_MPLS_LABEL:
+        flow_set_mpls_label(flow, value->be32);
+        break;
+
+    case MFF_MPLS_TC:
+        flow_set_mpls_tc(flow, value->u8);
+        break;
+
+    case MFF_MPLS_STACK:
+        flow_set_mpls_stack(flow, value->u8);
+        break;
+
     case MFF_IPV4_SRC:
         flow->nw_src = value->be32;
         break;
@@ -1439,6 +1536,18 @@ mf_set_wild(const struct mf_field *mf, struct match *match)
         match_set_any_pcp(match);
         break;
 
+    case MFF_MPLS_LABEL:
+        match_set_any_mpls_label(match);
+        break;
+
+    case MFF_MPLS_TC:
+        match_set_any_mpls_tc(match);
+        break;
+
+    case MFF_MPLS_STACK:
+        match_set_any_mpls_stack(match);
+        break;
+
     case MFF_IPV4_SRC:
     case MFF_ARP_SPA:
         match_set_nw_src_masked(match, htonl(0), htonl(0));
@@ -1564,6 +1673,9 @@ mf_set(const struct mf_field *mf,
     case MFF_DL_VLAN:
     case MFF_DL_VLAN_PCP:
     case MFF_VLAN_PCP:
+    case MFF_MPLS_LABEL:
+    case MFF_MPLS_TC:
+    case MFF_MPLS_STACK:
     case MFF_IP_PROTO:
     case MFF_IP_TTL:
     case MFF_IP_DSCP:
@@ -1798,6 +1910,18 @@ mf_random_value(const struct mf_field *mf, union mf_value *value)
         value->u8 &= 0x07;
         break;
 
+    case MFF_MPLS_LABEL:
+        value->be32 &= htonl(MPLS_LABEL_MASK >> MPLS_LABEL_SHIFT);
+        break;
+
+    case MFF_MPLS_TC:
+        value->u8 &= 0x07;
+        break;
+
+    case MFF_MPLS_STACK:
+        value->u8 &= 0x01;
+        break;
+
     case MFF_N_IDS:
     default:
         NOT_REACHED();
diff --git a/lib/meta-flow.h b/lib/meta-flow.h
index 60bfeca..5fdfb00 100644
--- a/lib/meta-flow.h
+++ b/lib/meta-flow.h
@@ -71,6 +71,11 @@ enum mf_field_id {
     MFF_DL_VLAN_PCP,            /* u8 (OpenFlow 1.0 compatibility) */
     MFF_VLAN_PCP,               /* be16 (OpenFlow 1.2 compatibility) */
 
+    /* L2.5 */
+    MFF_MPLS_LABEL,             /* be32 */
+    MFF_MPLS_TC,                /* u8 */
+    MFF_MPLS_STACK,             /* u8 */
+
     /* L3. */
     MFF_IPV4_SRC,               /* be32 */
     MFF_IPV4_DST,               /* be32 */
@@ -157,6 +162,7 @@ enum mf_prereqs {
     /* L2 requirements. */
     MFP_ARP,
     MFP_VLAN_VID,
+    MFP_MPLS,
     MFP_IPV4,
     MFP_IPV6,
     MFP_IP_ANY,
@@ -212,6 +218,9 @@ struct mf_field {
      *     - "dl_vlan_pcp" is 1 byte but only 3 bits.
      *     - "is_frag" is 1 byte but only 2 bits.
      *     - "ipv6_label" is 4 bytes but only 20 bits.
+     *     - "mpls_label" is 4 bytes but only 20 bits.
+     *     - "mpls_tc"    is 1 byte but only 3 bits.
+     *     - "mpls_stack" is 1 byte but only 1 bit.
      */
     unsigned int n_bytes;       /* Width of the field in bytes. */
     unsigned int n_bits;        /* Number of significant bits in field. */
diff --git a/lib/nx-match.c b/lib/nx-match.c
index 4254747..7d57418 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -547,7 +547,7 @@ nx_put_raw(struct ofpbuf *b, bool oxm, const struct match *match,
     int match_len;
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 17);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 18);
 
     /* Metadata. */
     if (match->wc.masks.in_port) {
@@ -589,6 +589,26 @@ nx_put_raw(struct ofpbuf *b, bool oxm, const struct match *match,
                     match->wc.masks.vlan_tci);
     }
 
+
+    /* MPLS. */
+    if (flow->dl_type == htons(ETH_TYPE_MPLS) ||
+        flow->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
+        if (match->wc.masks.mpls_lse & htonl(MPLS_TC_MASK)) {
+            nxm_put_8(b, oxm ? OXM_OF_MPLS_TC : NXM_NX_MPLS_TC,
+                      mpls_lse_to_tc(flow->mpls_lse));
+        }
+
+        if (match->wc.masks.mpls_lse & htonl(MPLS_STACK_MASK)) {
+            nxm_put_8(b, oxm ? OXM_OF_MPLS_STACK : NXM_NX_MPLS_STACK,
+                      mpls_lse_to_stack(flow->mpls_lse));
+        }
+
+        if (match->wc.masks.mpls_lse & htonl(MPLS_LABEL_MASK)) {
+            nxm_put_32(b, oxm ? OXM_OF_MPLS_LABEL : NXM_NX_MPLS_LABEL,
+                 htonl(mpls_lse_to_label(flow->mpls_lse)));
+        }
+    }
+
     /* L3. */
     if (flow->dl_type == htons(ETH_TYPE_IP)) {
         /* IP. */
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 901dac3..8e351ef 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -18,6 +18,7 @@
 #include <arpa/inet.h>
 #include "odp-util.h"
 #include <errno.h>
+#include <assert.h>
 #include <inttypes.h>
 #include <math.h>
 #include <netinet/in.h>
@@ -106,6 +107,8 @@ ovs_key_attr_to_string(enum ovs_key_attr attr)
     case OVS_KEY_ATTR_ARP: return "arp";
     case OVS_KEY_ATTR_ND: return "nd";
     case OVS_KEY_ATTR_TUN_ID: return "tun_id";
+    case OVS_KEY_ATTR_MPLS: return "mpls";
+    case OVS_KEY_ATTR_INNER_MPLS: return "inner_mpls";
 
     case __OVS_KEY_ATTR_MAX:
     default:
@@ -273,6 +276,26 @@ format_vlan_tci(struct ds *ds, ovs_be16 vlan_tci)
 }
 
 static void
+format_mpls_lse(struct ds *ds, ovs_be32 mpls_lse)
+{
+    ds_put_format(ds, "label=%"PRIu32",tc=%d,ttl=%d,bos=%d",
+                  mpls_lse_to_label(mpls_lse),
+                  mpls_lse_to_tc(mpls_lse),
+                  mpls_lse_to_ttl(mpls_lse),
+                  mpls_lse_to_stack(mpls_lse));
+}
+
+static ovs_be32
+format_mpls_lse_values(int mpls_label, int mpls_tc,
+                       int mpls_ttl, int mpls_stack)
+{
+    return(htonl((mpls_label << MPLS_LABEL_SHIFT) |
+                 (mpls_tc << MPLS_TC_SHIFT)       |
+                 (mpls_ttl << MPLS_TTL_SHIFT)     |
+                 (mpls_stack << MPLS_STACK_SHIFT)));
+}
+
+static void
 format_odp_action(struct ds *ds, const struct nlattr *a)
 {
     int expected_len;
@@ -614,6 +637,8 @@ odp_flow_key_attr_len(uint16_t type)
     case OVS_KEY_ATTR_ICMPV6: return sizeof(struct ovs_key_icmpv6);
     case OVS_KEY_ATTR_ARP: return sizeof(struct ovs_key_arp);
     case OVS_KEY_ATTR_ND: return sizeof(struct ovs_key_nd);
+    case OVS_KEY_ATTR_MPLS: return sizeof(ovs_be32);
+    case OVS_KEY_ATTR_INNER_MPLS: return sizeof(ovs_be32);
 
     case OVS_KEY_ATTR_UNSPEC:
     case __OVS_KEY_ATTR_MAX:
@@ -715,6 +740,18 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds)
         ds_put_char(ds, ')');
         break;
 
+    case OVS_KEY_ATTR_MPLS:
+        ds_put_char(ds, '(');
+        format_mpls_lse(ds, nl_attr_get_be32(a));
+        ds_put_char(ds, ')');
+        break;
+
+    case OVS_KEY_ATTR_INNER_MPLS:
+        ds_put_char(ds, '(');
+        format_mpls_lse(ds, nl_attr_get_be32(a));
+        ds_put_char(ds, ')');
+        break;
+
     case OVS_KEY_ATTR_ETHERTYPE:
         ds_put_format(ds, "(0x%04"PRIx16")",
                       ntohs(nl_attr_get_be16(a)));
@@ -984,6 +1021,36 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
     }
 
     {
+        int mpls_label, mpls_tc, mpls_ttl, mpls_stack;
+        ovs_be32 mpls_lse;
+        int n = -1;
+
+        if (sscanf(s, "mpls(label=%"SCNi32",tc=%i,ttl=%i,bos=%i)%n",
+                    &mpls_label, &mpls_tc, &mpls_ttl, &mpls_stack, &n) > 0 &&
+                    n > 0) {
+            mpls_lse = format_mpls_lse_values(mpls_label, mpls_tc,
+                                              mpls_ttl, mpls_stack);
+            nl_msg_put_be32(key, OVS_KEY_ATTR_MPLS, mpls_lse);
+            return n;
+        }
+    }
+
+    {
+        int mpls_label, mpls_tc, mpls_ttl, mpls_stack;
+        ovs_be32 mpls_lse;
+        int n = -1;
+
+        if (sscanf(s, "inner_mpls(label=%"SCNi32",tc=%i,ttl=%i,bos=%i)%n",
+                    &mpls_label, &mpls_tc, &mpls_ttl, &mpls_stack, &n) > 0 &&
+                    n > 0) {
+            mpls_lse = format_mpls_lse_values(mpls_label, mpls_tc,
+                                              mpls_ttl, mpls_stack);
+            nl_msg_put_be32(key, OVS_KEY_ATTR_INNER_MPLS, mpls_lse);
+            return n;
+        }
+    }
+
+    {
         ovs_be32 ipv4_src;
         ovs_be32 ipv4_dst;
         int ipv4_proto;
@@ -1291,6 +1358,14 @@ odp_flow_key_from_flow(struct ofpbuf *buf, const struct flow *flow)
 
     nl_msg_put_be16(buf, OVS_KEY_ATTR_ETHERTYPE, flow->dl_type);
 
+    if (flow->dl_type == htons(ETH_TYPE_MPLS) ||
+        flow->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
+        nl_msg_put_be32(buf, OVS_KEY_ATTR_MPLS, flow->mpls_lse);
+        if (flow->inner_mpls_lse != htonl(0)) {
+            nl_msg_put_be32(buf, OVS_KEY_ATTR_INNER_MPLS, flow->inner_mpls_lse);
+        }
+    }
+
     if (flow->dl_type == htons(ETH_TYPE_IP)) {
         struct ovs_key_ipv4 *ipv4_key;
 
@@ -1510,6 +1585,20 @@ check_expectations(uint64_t present_attrs, int out_of_range_attr,
     return ODP_FIT_PERFECT;
 }
 
+static enum odp_key_fitness
+check_expectations_mpls(uint64_t present_attrs, int out_of_range_attr,
+                        uint64_t expected_attrs,
+                        const struct nlattr *key, size_t key_len)
+{
+   /* ODP_FIT_TOO_LITTLE to force slow path as
+    * datapath is not aware of MPLS matches yet */
+    return MAX(ODP_FIT_TOO_LITTLE, check_expectations(present_attrs,
+                                                      out_of_range_attr,
+                                                      expected_attrs,
+                                                      key, key_len));
+}
+
+
 static bool
 parse_ethertype(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
                 uint64_t present_attrs, uint64_t *expected_attrs,
@@ -1533,13 +1622,93 @@ parse_ethertype(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
 
 static enum odp_key_fitness
 parse_l3_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
+                   uint64_t present_attrs, int out_of_range_attr,
+                   uint64_t expected_attrs, struct flow *flow,
+                   const struct nlattr *key, size_t key_len);
+
+/* Parse MPLS header attributes. */
+static enum odp_key_fitness
+parse_mpls_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
+                   uint64_t present_attrs, int out_of_range_attr,
+                   uint64_t expected_attrs, struct flow *flow,
+                   const struct nlattr *key, size_t key_len)
+{
+    enum odp_key_fitness fitness, encap_fitness;
+    ovs_be32 mpls_lse;
+    ovs_be16 dl_type;
+
+    /* Calulate fitness of outer attributes. */
+    expected_attrs |= (UINT64_C(1) << OVS_KEY_ATTR_MPLS);
+    fitness = check_expectations(present_attrs, out_of_range_attr,
+                                 expected_attrs, key, key_len);
+
+    /* Get the MPLS LSE value. */
+    if (!(present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_MPLS))) {
+        return ODP_FIT_TOO_LITTLE;
+    }
+    mpls_lse = nl_attr_get_be32(attrs[OVS_KEY_ATTR_MPLS]);
+    if (mpls_lse == htonl(0)) {
+        /* Corner case for a truncated MPLS header. */
+        return fitness;
+    }
+
+    /* Set mpls_lse. */
+    flow->mpls_lse = mpls_lse;
+
+    /* Handle Inner MPLS lse. */
+    if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_INNER_MPLS)) {
+        expected_attrs |= (UINT64_C(1) << OVS_KEY_ATTR_INNER_MPLS);
+        fitness = check_expectations_mpls(present_attrs, out_of_range_attr,
+                                          expected_attrs, key, key_len);
+        mpls_lse = nl_attr_get_be32(attrs[OVS_KEY_ATTR_INNER_MPLS]);
+        if (mpls_lse == htonl(0)) {
+            /* Corner case for a truncated MPLS header. */
+            return MAX(ODP_FIT_TOO_LITTLE, fitness);
+        }
+        /* Set mpls_lse. */
+        flow->inner_mpls_lse = mpls_lse;
+    }
+
+    fitness = check_expectations_mpls(present_attrs, out_of_range_attr,
+                                      expected_attrs, key, key_len);
+
+    /* Try to guess what the encapsualted ethernet type was
+     * in order to try and fill out the flow more fully */
+    dl_type = flow->dl_type;
+    if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_IPV4)) {
+        flow->dl_type = htons(ETH_TYPE_IP);
+    } else if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_IPV6)) {
+        flow->dl_type = htons(ETH_TYPE_IPV6);
+    } else if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_ARP)) {
+        flow->dl_type = htons(ETH_TYPE_ARP);
+    } else {
+        /* Nothing to work with, abandon hope of further processing */
+        return fitness;
+    }
+    encap_fitness = parse_l3_onward(attrs, present_attrs,
+                                    out_of_range_attr, expected_attrs,
+                                    flow, key, key_len);
+    flow->encap_dl_type = flow->dl_type;
+    flow->dl_type = dl_type;
+
+    /* The overall fitness is the worse of the outer and inner attributes. */
+    return MAX(encap_fitness, fitness);
+}
+
+static enum odp_key_fitness
+parse_l3_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
                 uint64_t present_attrs, int out_of_range_attr,
                 uint64_t expected_attrs, struct flow *flow,
                 const struct nlattr *key, size_t key_len)
 {
     static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
 
-    if (flow->dl_type == htons(ETH_TYPE_IP)) {
+    /* Parse MPLS label stack entry */
+    if (flow->dl_type == htons(ETH_TYPE_MPLS) ||
+        flow->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
+        return parse_mpls_onward(attrs, present_attrs, out_of_range_attr,
+                                  expected_attrs, flow, key, key_len);
+    } else if (flow->dl_type == htons(ETH_TYPE_IP)) {
         expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_IPV4;
         if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_IPV4)) {
             const struct ovs_key_ipv4 *ipv4_key;
@@ -1709,8 +1878,10 @@ parse_8021q_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
     if (!parse_ethertype(attrs, present_attrs, &expected_attrs, flow)) {
         return ODP_FIT_ERROR;
     }
-    encap_fitness = parse_l3_onward(attrs, present_attrs, out_of_range_attr,
-                                    expected_attrs, flow, key, key_len);
+
+    encap_fitness = parse_l3_onward(attrs, present_attrs,
+                                    out_of_range_attr, expected_attrs,
+                                    flow, key, key_len);
 
     /* The overall fitness is the worse of the outer and inner attributes. */
     return MAX(fitness, encap_fitness);
@@ -1789,6 +1960,7 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
         return parse_8021q_onward(attrs, present_attrs, out_of_range_attr,
                                   expected_attrs, flow, key, key_len);
     }
+
     return parse_l3_onward(attrs, present_attrs, out_of_range_attr,
                            expected_attrs, flow, key, key_len);
 }
@@ -1955,7 +2127,6 @@ commit_set_ipv6_action(const struct flow *flow, struct flow *base,
     commit_set_action(odp_actions, OVS_KEY_ATTR_IPV6,
                       &ipv6_key, sizeof(ipv6_key));
 }
-
 static void
 commit_set_nw_action(const struct flow *flow, struct flow *base,
                      struct ofpbuf *odp_actions)
diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
index a58f8db..4d26105 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -378,6 +378,50 @@ ofpact_from_nxast(const union ofp_action *a, enum ofputil_action_code code,
     case OFPUTIL_NXAST_CONTROLLER:
         controller_from_openflow((const struct nx_action_controller *) a, out);
         break;
+
+    case OFPUTIL_NXAST_COPY_TTL_OUT:
+        ofpact_put_COPY_TTL_OUT(out);
+        break;
+
+    case OFPUTIL_NXAST_COPY_TTL_IN:
+        ofpact_put_COPY_TTL_IN(out);
+        break;
+
+    case OFPUTIL_NXAST_SET_MPLS_LABEL: {
+        struct nx_action_mpls_label *nxaml =
+            (struct nx_action_mpls_label *)a;
+        ofpact_put_SET_MPLS_LABEL(out)->mpls_label = nxaml->mpls_label;
+        break;
+    }
+
+    case OFPUTIL_NXAST_SET_MPLS_TC: {
+        struct nx_action_mpls_tc *nxamt = (struct nx_action_mpls_tc *)a;
+        ofpact_put_SET_MPLS_TC(out)->mpls_tc = nxamt->mpls_tc;
+        break;
+    }
+
+    case OFPUTIL_NXAST_SET_MPLS_TTL: {
+        struct nx_action_mpls_ttl *nxamt = (struct nx_action_mpls_ttl *)a;
+        ofpact_put_SET_MPLS_TTL(out)->mpls_ttl = nxamt->mpls_ttl;
+        break;
+    }
+
+    case OFPUTIL_NXAST_DEC_MPLS_TTL: {
+        ofpact_put_DEC_MPLS_TTL(out);
+        break;
+    }
+
+    case OFPUTIL_NXAST_PUSH_MPLS: {
+        struct nx_action_push *nxap = (struct nx_action_push *)a;
+        ofpact_put_PUSH_MPLS(out)->ethertype = nxap->ethertype;
+        break;
+    }
+
+    case OFPUTIL_NXAST_POP_MPLS: {
+        struct nx_action_pop_mpls *nxapm = (struct nx_action_pop_mpls *)a;
+        ofpact_put_POP_MPLS(out)->ethertype = nxapm->ethertype;
+        break;
+    }
     }
 
     return error;
@@ -722,6 +766,51 @@ ofpact_from_openflow11(const union ofp_action *a, struct ofpbuf *out)
         return nxm_reg_load_from_openflow12_set_field(
             (const struct ofp12_action_set_field *)a, out);
 
+    case OFPUTIL_OFPAT11_SET_MPLS_LABEL: {
+        struct ofp11_action_mpls_label *oaml =
+            (struct ofp11_action_mpls_label *)a;
+        ofpact_put_SET_MPLS_LABEL(out)->mpls_label = oaml->mpls_label;
+        break;
+    }
+
+    case OFPUTIL_OFPAT11_SET_MPLS_TC: {
+        struct ofp11_action_mpls_tc *oamt = (struct ofp11_action_mpls_tc *)a;
+        ofpact_put_SET_MPLS_TC(out)->mpls_tc = oamt->mpls_tc;
+        break;
+    }
+
+    case OFPUTIL_OFPAT11_SET_MPLS_TTL: {
+        struct ofp11_action_mpls_ttl *oasmt =
+            (struct ofp11_action_mpls_ttl *)a;
+        ofpact_put_SET_MPLS_TTL(out)->mpls_ttl = oasmt->mpls_ttl;
+        break;
+    }
+
+    case OFPUTIL_OFPAT11_DEC_MPLS_TTL:
+        ofpact_put_DEC_MPLS_TTL(out);
+        break;
+
+    case OFPUTIL_OFPAT11_PUSH_MPLS: {
+        struct ofp11_action_push *oap = (struct ofp11_action_push *)a;
+        ofpact_put_PUSH_MPLS(out)->ethertype = oap->ethertype;
+        break;
+    }
+
+    case OFPUTIL_OFPAT11_POP_MPLS: {
+        struct ofp11_action_pop_mpls *oapm = (struct ofp11_action_pop_mpls *)a;
+        ofpact_put_POP_MPLS(out)->ethertype = oapm->ethertype;
+        break;
+    }
+
+    case OFPUTIL_OFPAT11_COPY_TTL_OUT:
+        ofpact_put_COPY_TTL_OUT(out);
+        break;
+
+    case OFPUTIL_OFPAT11_COPY_TTL_IN:
+        ofpact_put_COPY_TTL_IN(out);
+        break;
+
+
 #define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) case OFPUTIL_##ENUM:
 #include "ofp-util.def"
         return ofpact_from_nxast(a, code, out);
@@ -1040,6 +1129,54 @@ ofpact_check__(const struct ofpact *a, const struct flow *flow, int max_ports)
     case OFPACT_EXIT:
         return 0;
 
+    case OFPACT_PUSH_MPLS: {
+        ovs_be16 etype = ofpact_get_PUSH_MPLS(a)->ethertype;
+        if (etype != htons(ETH_TYPE_MPLS) &&
+            etype != htons(ETH_TYPE_MPLS_MCAST)) {
+            return OFPERR_OFPBAC_BAD_ARGUMENT;
+        }
+        return 0;
+    }
+
+    case OFPACT_POP_MPLS: {
+        ovs_be16 etype = ofpact_get_POP_MPLS(a)->ethertype;
+        if (etype == htons(ETH_TYPE_MPLS) ||
+            etype == htons(ETH_TYPE_MPLS_MCAST)) {
+            return OFPERR_OFPBAC_BAD_ARGUMENT;
+        }
+        return 0;
+    }
+
+    case OFPACT_SET_MPLS_LABEL: {
+        ovs_be32 mpls_label = ofpact_get_SET_MPLS_LABEL(a)->mpls_label;
+        if (mpls_label & ~htonl(MPLS_LABEL_MASK >> MPLS_LABEL_SHIFT)) {
+            return OFPERR_OFPBAC_BAD_ARGUMENT;
+        }
+        return 0;
+    }
+
+    case OFPACT_SET_MPLS_TC: {
+        uint8_t mpls_tc = ofpact_get_SET_MPLS_TC(a)->mpls_tc;
+        if (mpls_tc & ~(MPLS_TC_MASK >> MPLS_TC_SHIFT)) {
+            return OFPERR_OFPBAC_BAD_ARGUMENT;
+        }
+        return 0;
+    }
+
+    case OFPACT_SET_MPLS_TTL: {
+        uint8_t mpls_ttl = ofpact_get_SET_MPLS_TTL(a)->mpls_ttl;
+        if (mpls_ttl == 0 || mpls_ttl == 1) {
+            return OFPERR_OFPBAC_BAD_ARGUMENT;
+        }
+        return 0;
+    }
+
+    case OFPACT_COPY_TTL_OUT:
+    case OFPACT_COPY_TTL_IN:
+    case OFPACT_DEC_MPLS_TTL:
+        return 0;
+
+
     default:
         NOT_REACHED();
     }
@@ -1243,6 +1380,43 @@ ofpact_to_nxast(const struct ofpact *a, struct ofpbuf *out)
         ofputil_put_NXAST_EXIT(out);
         break;
 
+    case OFPACT_SET_MPLS_LABEL:
+        ofputil_put_NXAST_SET_MPLS_LABEL(out)->mpls_label =
+            ofpact_get_SET_MPLS_LABEL(a)->mpls_label;
+        break;
+
+    case OFPACT_SET_MPLS_TC:
+        ofputil_put_NXAST_SET_MPLS_TC(out)->mpls_tc =
+            ofpact_get_SET_MPLS_TC(a)->mpls_tc;
+        break;
+
+    case OFPACT_SET_MPLS_TTL:
+        ofputil_put_NXAST_SET_MPLS_TTL(out)->mpls_ttl =
+            ofpact_get_SET_MPLS_TTL(a)->mpls_ttl;
+        break;
+
+    case OFPACT_DEC_MPLS_TTL:
+        ofputil_put_NXAST_DEC_MPLS_TTL(out);
+        break;
+
+    case OFPACT_PUSH_MPLS:
+        ofputil_put_NXAST_PUSH_MPLS(out)->ethertype =
+            ofpact_get_PUSH_MPLS(a)->ethertype;
+        break;
+
+    case OFPACT_POP_MPLS:
+        ofputil_put_NXAST_POP_MPLS(out)->ethertype =
+            ofpact_get_POP_MPLS(a)->ethertype;
+        break;
+
+    case OFPACT_COPY_TTL_OUT:
+        ofputil_put_NXAST_COPY_TTL_OUT(out);
+        break;
+
+    case OFPACT_COPY_TTL_IN:
+        ofputil_put_NXAST_COPY_TTL_IN(out);
+        break;
+
     case OFPACT_OUTPUT:
     case OFPACT_ENQUEUE:
     case OFPACT_SET_VLAN_VID:
@@ -1360,6 +1534,14 @@ ofpact_to_openflow10(const struct ofpact *a, struct ofpbuf *out)
     case OFPACT_AUTOPATH:
     case OFPACT_NOTE:
     case OFPACT_EXIT:
+    case OFPACT_SET_MPLS_LABEL:
+    case OFPACT_SET_MPLS_TC:
+    case OFPACT_SET_MPLS_TTL:
+    case OFPACT_DEC_MPLS_TTL:
+    case OFPACT_PUSH_MPLS:
+    case OFPACT_POP_MPLS:
+    case OFPACT_COPY_TTL_OUT:
+    case OFPACT_COPY_TTL_IN:
         ofpact_to_nxast(a, out);
         break;
     }
@@ -1452,6 +1634,33 @@ ofpact_to_openflow11(const struct ofpact *a, struct ofpbuf *out)
             = htons(ofpact_get_SET_L4_DST_PORT(a)->port);
         break;
 
+    case OFPACT_SET_MPLS_TTL:
+        ofputil_put_OFPAT11_SET_MPLS_TTL(out)->mpls_ttl =
+            ofpact_get_SET_MPLS_TTL(a)->mpls_ttl;
+        break;
+
+    case OFPACT_DEC_MPLS_TTL:
+        ofputil_put_OFPAT11_DEC_MPLS_TTL(out);
+        break;
+
+    case OFPACT_PUSH_MPLS:
+        ofputil_put_OFPAT11_PUSH_MPLS(out)->ethertype =
+            ofpact_get_PUSH_MPLS(a)->ethertype;
+        break;
+
+    case OFPACT_POP_MPLS:
+        ofputil_put_OFPAT11_POP_MPLS(out)->ethertype =
+            ofpact_get_POP_MPLS(a)->ethertype;
+        break;
+
+    case OFPACT_COPY_TTL_OUT:
+        ofputil_put_OFPAT11_COPY_TTL_OUT(out);
+        break;
+
+    case OFPACT_COPY_TTL_IN:
+        ofputil_put_OFPAT11_COPY_TTL_IN(out);
+        break;
+
     case OFPACT_CONTROLLER:
     case OFPACT_OUTPUT_REG:
     case OFPACT_BUNDLE:
@@ -1468,6 +1677,8 @@ ofpact_to_openflow11(const struct ofpact *a, struct ofpbuf *out)
     case OFPACT_AUTOPATH:
     case OFPACT_NOTE:
     case OFPACT_EXIT:
+    case OFPACT_SET_MPLS_LABEL:
+    case OFPACT_SET_MPLS_TC:
         ofpact_to_nxast(a, out);
         break;
     }
@@ -1549,6 +1760,15 @@ ofpact_outputs_to_port(const struct ofpact *ofpact, uint16_t port)
     case OFPACT_AUTOPATH:
     case OFPACT_NOTE:
     case OFPACT_EXIT:
+    case OFPACT_COPY_TTL_OUT:
+    case OFPACT_COPY_TTL_IN:
+    case OFPACT_SET_MPLS_LABEL:
+    case OFPACT_SET_MPLS_TC:
+    case OFPACT_SET_MPLS_TTL:
+    case OFPACT_DEC_MPLS_TTL:
+    case OFPACT_PUSH_MPLS:
+    case OFPACT_POP_MPLS:
+
     default:
         return false;
     }
@@ -1639,6 +1859,13 @@ ofpact_format(const struct ofpact *a, struct ds *s)
     const struct ofpact_controller *controller;
     const struct ofpact_tunnel *tunnel;
     uint16_t port;
+#if 0
+    const struct nx_action_mpls_label *naml;
+    const struct nx_action_push_mpls *nampush;
+    const struct nx_action_pop_mpls  *nampop;
+    const struct nx_action_mpls_tc  *namtc;
+    const struct nx_action_mpls_ttl  *namttl;
+#endif
 
     switch (a->type) {
     case OFPACT_OUTPUT:
@@ -1812,6 +2039,44 @@ ofpact_format(const struct ofpact *a, struct ds *s)
         print_note(ofpact_get_NOTE(a), s);
         break;
 
+    case OFPACT_COPY_TTL_OUT:
+        ds_put_cstr(s, "copy_ttl_out");
+        break;
+
+    case OFPACT_COPY_TTL_IN:
+        ds_put_cstr(s, "copy_ttl_in");
+        break;
+
+    case OFPACT_SET_MPLS_LABEL:
+        ds_put_format(s, "set_mpls_label:%"PRIu32,
+                      ntohl(ofpact_get_SET_MPLS_LABEL(a)->mpls_label));
+        break;
+
+    case OFPACT_SET_MPLS_TC:
+        ds_put_format(s, "set_mpls_tc:%"PRIu8,
+                      ofpact_get_SET_MPLS_TC(a)->mpls_tc);
+        break;
+
+    case OFPACT_SET_MPLS_TTL:
+        ds_put_format(s, "set_mpls_ttl:%"PRIu8,
+                      ofpact_get_SET_MPLS_TTL(a)->mpls_ttl);
+        break;
+
+    case OFPACT_DEC_MPLS_TTL:
+        ds_put_cstr(s, "dec_mpls_ttl");
+        break;
+
+    case OFPACT_PUSH_MPLS:
+        ds_put_format(s, "push_mpls:0x%04"PRIx16,
+                      ntohs(ofpact_get_PUSH_MPLS(a)->ethertype));
+        break;
+
+    case OFPACT_POP_MPLS:
+        ds_put_format(s, "pop_mpls:0x%04"PRIx16,
+                      ntohs(ofpact_get_POP_MPLS(a)->ethertype));
+        break;
+
+
     case OFPACT_EXIT:
         ds_put_cstr(s, "exit");
         break;
diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h
index fd53e62..c372918 100644
--- a/lib/ofp-actions.h
+++ b/lib/ofp-actions.h
@@ -70,6 +70,14 @@
     DEFINE_OFPACT(REG_MOVE,        ofpact_reg_move,      ofpact)    \
     DEFINE_OFPACT(REG_LOAD,        ofpact_reg_load,      ofpact)    \
     DEFINE_OFPACT(DEC_TTL,         ofpact_cnt_ids,       cnt_ids)   \
+    DEFINE_OFPACT(COPY_TTL_OUT,    ofpact_null,          ofpact)    \
+    DEFINE_OFPACT(COPY_TTL_IN,     ofpact_null,          ofpact)    \
+    DEFINE_OFPACT(SET_MPLS_LABEL,  ofpact_mpls_label,    ofpact)    \
+    DEFINE_OFPACT(SET_MPLS_TC,     ofpact_mpls_tc,       ofpact)    \
+    DEFINE_OFPACT(SET_MPLS_TTL,    ofpact_mpls_ttl,      ofpact)    \
+    DEFINE_OFPACT(DEC_MPLS_TTL,    ofpact_null,          ofpact)    \
+    DEFINE_OFPACT(PUSH_MPLS,       ofpact_push,          ofpact)    \
+    DEFINE_OFPACT(POP_MPLS,        ofpact_pop_mpls,      ofpact)    \
                                                                     \
     /* Metadata. */                                                 \
     DEFINE_OFPACT(SET_TUNNEL,      ofpact_tunnel,        ofpact)    \
@@ -302,6 +310,54 @@ struct ofpact_reg_load {
     union mf_subvalue subvalue; /* Least-significant bits are used. */
 };
 
+/* OFPACT_SET_MPLS_LABEL
+ *
+ * used for NXAST_SET_MPLS_LABEL */
+struct ofpact_mpls_label {
+    struct ofpact ofpact;
+    ovs_be32 mpls_label;        /* lower 20 bits */
+};
+
+/* OFPACT_SET_MPLS_TC
+ *
+ * used for NXAST_SET_MPLS_TC */
+struct ofpact_mpls_tc {
+    struct ofpact ofpact;
+    uint8_t mpls_tc;           /* lower 3 bits */
+};
+
+/* OFPACT_SET_MPLS_STACK
+ *
+ * used for NXAST_SET_MPLS_STACK */
+struct ofpact_mpls_stack {
+    struct ofpact ofpact;
+    uint8_t mpls_stack;         /* lower 1 bits */
+};
+
+/* OFPACT_SET_MPLS_TTL
+ *
+ * used for NXAST_SET_MPLS_TTL */
+struct ofpact_mpls_ttl {
+    struct ofpact ofpact;
+    uint8_t mpls_ttl;           /* 8 bits */
+};
+
+/* OFPACT_PUSH_VLAN/MPLS/PBB
+ *
+ * used for NXAST_PUSH_MPLS, OFPAT13_PUSH_VLAN/MPSL/BPP */
+struct ofpact_push {
+    struct ofpact ofpact;
+    ovs_be16 ethertype;
+};
+
+/* OFPACT_POP_MPLS
+ *
+ * used for NXAST_POP_MPLS */
+struct ofpact_pop_mpls {
+    struct ofpact ofpact;
+    ovs_be16 ethertype;
+};
+
 /* OFPACT_SET_TUNNEL.
  *
  * Used for NXAST_SET_TUNNEL, NXAST_SET_TUNNEL64. */
diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
index 2d2daa4..2140e5f 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -517,6 +517,48 @@ parse_named_action(enum ofputil_action_code code, const struct flow *flow,
     case OFPUTIL_NXAST_CONTROLLER:
         parse_controller(ofpacts, arg);
         break;
+
+    case OFPUTIL_OFPAT11_COPY_TTL_OUT:
+    case OFPUTIL_NXAST_COPY_TTL_OUT:
+        ofpact_put_COPY_TTL_OUT(ofpacts);
+        break;
+
+    case OFPUTIL_OFPAT11_COPY_TTL_IN:
+    case OFPUTIL_NXAST_COPY_TTL_IN:
+        ofpact_put_COPY_TTL_IN(ofpacts);
+        break;
+
+    case OFPUTIL_OFPAT11_SET_MPLS_LABEL:
+    case OFPUTIL_NXAST_SET_MPLS_LABEL:
+        ofpact_put_SET_MPLS_LABEL(ofpacts)->mpls_label = htonl(str_to_u32(arg));
+        break;
+
+    case OFPUTIL_OFPAT11_SET_MPLS_TC:
+    case OFPUTIL_NXAST_SET_MPLS_TC:
+        ofpact_put_SET_MPLS_TC(ofpacts)->mpls_tc = str_to_u32(arg);
+        break;
+
+    case OFPUTIL_OFPAT11_SET_MPLS_TTL:
+    case OFPUTIL_NXAST_SET_MPLS_TTL:
+        ofpact_put_SET_MPLS_TTL(ofpacts)->mpls_ttl = str_to_u32(arg);
+        break;
+
+    case OFPUTIL_OFPAT11_DEC_MPLS_TTL:
+    case OFPUTIL_NXAST_DEC_MPLS_TTL:
+        ofpact_put_DEC_MPLS_TTL(ofpacts);
+        break;
+
+    case OFPUTIL_OFPAT11_PUSH_MPLS:
+    case OFPUTIL_NXAST_PUSH_MPLS:
+        ofpact_put_PUSH_MPLS(ofpacts)->ethertype =
+            htons(str_to_u16(arg, "push_mpls"));
+        break;
+
+    case OFPUTIL_OFPAT11_POP_MPLS:
+    case OFPUTIL_NXAST_POP_MPLS:
+        ofpact_put_POP_MPLS(ofpacts)->ethertype =
+            htons(str_to_u16(arg, "pop_mpls"));
+        break;
     }
 }
 
@@ -574,6 +616,8 @@ parse_protocol(const char *name, const struct protocol **p_out)
         { "icmp6", ETH_TYPE_IPV6, IPPROTO_ICMPV6 },
         { "tcp6", ETH_TYPE_IPV6, IPPROTO_TCP },
         { "udp6", ETH_TYPE_IPV6, IPPROTO_UDP },
+        { "mpls", ETH_TYPE_MPLS, 0 },
+        { "mplsm", ETH_TYPE_MPLS_MCAST, 0 },
     };
     const struct protocol *p;
 
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index 6789625..5cee9ae 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -617,6 +617,10 @@ ofp10_match_to_string(const struct ofp10_match *om, int verbosity)
             }
         } else if (om->dl_type == htons(ETH_TYPE_ARP)) {
             ds_put_cstr(&f, "arp,");
+        } else if (om->dl_type == htons(ETH_TYPE_MPLS)) {
+            ds_put_cstr(&f, "mpls,");
+        } else if (om->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
+            ds_put_cstr(&f, "mplsm,");
         } else {
             skip_type = false;
         }
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index 366dcae..64fa786 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -84,7 +84,7 @@ ofputil_netmask_to_wcbits(ovs_be32 netmask)
 void
 ofputil_wildcard_from_ofpfw10(uint32_t ofpfw, struct flow_wildcards *wc)
 {
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 17);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 18);
 
     /* Initialize most of wc. */
     flow_wildcards_init_catchall(wc);
@@ -139,7 +139,7 @@ ofputil_match_from_ofp10_match(const struct ofp10_match *ofmatch,
     uint32_t ofpfw = ntohl(ofmatch->wildcards) & OFPFW10_ALL;
 
     /* Initialize match->wc. */
-    memset(match->flow.zeros, 0, sizeof match->flow.zeros);
+    flow_zero_pad(&match->flow);
     ofputil_wildcard_from_ofpfw10(ofpfw, &match->wc);
 
     /* Initialize most of match->flow. */
@@ -900,7 +900,7 @@ ofputil_usable_protocols(const struct match *match)
 {
     const struct flow_wildcards *wc = &match->wc;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 17);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 18);
 
     /* NXM and OF1.1+ supports bitwise matching on ethernet addresses. */
     if (!eth_mask_is_exact(wc->masks.dl_src)
@@ -969,6 +969,21 @@ ofputil_usable_protocols(const struct match *match)
         return OFPUTIL_P_NXM_ANY;
     }
 
+    /* NXM and OF1.1+ support matching MPLS label */
+    if (wc->masks.mpls_lse & htonl(MPLS_LABEL_MASK)) {
+        return OFPUTIL_P_NXM_ANY;
+    }
+
+    /* NXM and OF1.1+ support matching MPLS TC */
+    if (wc->masks.mpls_lse & htonl(MPLS_TC_MASK)) {
+        return OFPUTIL_P_NXM_ANY;
+    }
+
+    /* NXM and OF1.1+ support matching MPLS stack flag */
+    if (wc->masks.mpls_lse & htonl(MPLS_STACK_MASK)) {
+        return OFPUTIL_P_NXM_ANY;
+    }
+
     /* Other formats can express this rule. */
     return OFPUTIL_P_ANY;
 }
@@ -3753,7 +3768,8 @@ ofputil_normalize_match__(struct match *match, bool may_log)
         MAY_ARP_SHA     = 1 << 4, /* arp_sha */
         MAY_ARP_THA     = 1 << 5, /* arp_tha */
         MAY_IPV6        = 1 << 6, /* ipv6_src, ipv6_dst, ipv6_label */
-        MAY_ND_TARGET   = 1 << 7  /* nd_target */
+        MAY_ND_TARGET   = 1 << 7, /* nd_target */
+        MAY_MPLS        = 1 << 8, /* mpls label and tc */
     } may_match;
 
     struct flow_wildcards wc;
@@ -3781,6 +3797,9 @@ ofputil_normalize_match__(struct match *match, bool may_log)
         }
     } else if (match->flow.dl_type == htons(ETH_TYPE_ARP)) {
         may_match = MAY_NW_PROTO | MAY_NW_ADDR | MAY_ARP_SHA | MAY_ARP_THA;
+    } else if (match->flow.dl_type == htons(ETH_TYPE_MPLS) ||
+               match->flow.dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
+        may_match = MAY_MPLS;
     } else {
         may_match = 0;
     }
@@ -3813,6 +3832,9 @@ ofputil_normalize_match__(struct match *match, bool may_log)
     if (!(may_match & MAY_ND_TARGET)) {
         wc.masks.nd_target = in6addr_any;
     }
+    if (!(may_match & MAY_MPLS)) {
+        wc.masks.mpls_lse = htonl(0);
+    }
 
     /* Log any changes. */
     if (!flow_wildcards_equal(&wc, &match->wc)) {
diff --git a/lib/ofp-util.def b/lib/ofp-util.def
index 4d451b0..c15c389 100644
--- a/lib/ofp-util.def
+++ b/lib/ofp-util.def
@@ -30,8 +30,16 @@ OFPAT11_ACTION(OFPAT11_SET_NW_TOS,   ofp_action_nw_tos,   0, "mod_nw_tos")
 //OFPAT11_ACTION(OFPAT11_SET_NW_ECN,   ofp11_action_nw_ecn, "0, mod_nw_ecn")
 OFPAT11_ACTION(OFPAT11_SET_TP_SRC,   ofp_action_tp_port,  0, "mod_tp_src")
 OFPAT11_ACTION(OFPAT11_SET_TP_DST,   ofp_action_tp_port,  0, "mod_tp_dst")
+OFPAT11_ACTION(OFPAT11_COPY_TTL_OUT, ofp_action_header,   0, "copy_ttl_out")
+OFPAT11_ACTION(OFPAT11_COPY_TTL_IN,  ofp_action_header,   0, "copy_ttl_in")
+OFPAT11_ACTION(OFPAT11_SET_MPLS_LABEL, ofp11_action_mpls_label, 0, "set_mpls_label")
+OFPAT11_ACTION(OFPAT11_SET_MPLS_TC,  ofp11_action_mpls_tc, 0, "set_mpls_tc")
+OFPAT11_ACTION(OFPAT11_SET_MPLS_TTL, ofp11_action_mpls_ttl, 0, "set_mpls_ttl")
+OFPAT11_ACTION(OFPAT11_DEC_MPLS_TTL, ofp_action_header,   0, "dec_mpls_ttl")
 //OFPAT11_ACTION(OFPAT11_PUSH_VLAN,    ofp11_action_push,   0, "push_vlan")
 //OFPAT11_ACTION(OFPAT11_POP_VLAN,     ofp_action_header,   0, "pop_vlan")
+OFPAT11_ACTION(OFPAT11_PUSH_MPLS,    ofp11_action_push,   0, "push_mpls")
+OFPAT11_ACTION(OFPAT11_POP_MPLS,     ofp11_action_pop_mpls, 0, "pop_mpls")
 //OFPAT11_ACTION(OFPAT11_SET_QUEUE,    ofp11_action_set_queue, 0, "set_queue")
 //OFPAT11_ACTION(OFPAT11_SET_NW_TTL,   ofp11_action_nw_ttl, 0, "set_nw_ttl")
 //OFPAT11_ACTION(OFPAT11_DEC_NW_TTL,   ofp_action_header,   0, "dec_ttl")
@@ -60,6 +68,14 @@ NXAST_ACTION(NXAST_DEC_TTL,         nx_action_header,       0, "dec_ttl")
 NXAST_ACTION(NXAST_FIN_TIMEOUT,     nx_action_fin_timeout,  0, "fin_timeout")
 NXAST_ACTION(NXAST_CONTROLLER,      nx_action_controller,   0, "controller")
 NXAST_ACTION(NXAST_DEC_TTL_CNT_IDS, nx_action_cnt_ids,      1, NULL)
+NXAST_ACTION(NXAST_COPY_TTL_OUT,    nx_action_header,       0, "copy_ttl_out")
+NXAST_ACTION(NXAST_COPY_TTL_IN,     nx_action_header,       0, "copy_ttl_in")
+NXAST_ACTION(NXAST_SET_MPLS_LABEL,  nx_action_mpls_label,   0, "set_mpls_label")
+NXAST_ACTION(NXAST_SET_MPLS_TC,     nx_action_mpls_tc,      0, "set_mpls_tc")
+NXAST_ACTION(NXAST_SET_MPLS_TTL,    nx_action_mpls_ttl,     0, "set_mpls_ttl")
+NXAST_ACTION(NXAST_DEC_MPLS_TTL,    nx_action_header,       0, "dec_mpls_ttl")
+NXAST_ACTION(NXAST_PUSH_MPLS,       nx_action_push,         0, "push_mpls")
+NXAST_ACTION(NXAST_POP_MPLS,        nx_action_pop_mpls,     0, "pop_mpls")
 
 #undef OFPAT10_ACTION
 #undef OFPAT11_ACTION
diff --git a/lib/ofpbuf.c b/lib/ofpbuf.c
index a7d4c73..9f5e908 100644
--- a/lib/ofpbuf.c
+++ b/lib/ofpbuf.c
@@ -30,7 +30,7 @@ ofpbuf_use__(struct ofpbuf *b, void *base, size_t allocated,
     b->allocated = allocated;
     b->source = source;
     b->size = 0;
-    b->l2 = b->l3 = b->l4 = b->l7 = NULL;
+    b->l2 = b->l2_5 = b->l3 = b->l4 = b->l7 = NULL;
     list_poison(&b->list_node);
     b->private_p = NULL;
 }
@@ -177,6 +177,9 @@ ofpbuf_clone_with_headroom(const struct ofpbuf *buffer, size_t headroom)
     if (buffer->l2) {
         new_buffer->l2 = (char *) buffer->l2 + data_delta;
     }
+    if (buffer->l2_5) {
+        new_buffer->l2_5 = (char *) buffer->l2_5 + data_delta;
+    }
     if (buffer->l3) {
         new_buffer->l3 = (char *) buffer->l3 + data_delta;
     }
@@ -296,6 +299,9 @@ ofpbuf_resize__(struct ofpbuf *b, size_t new_headroom, size_t new_tailroom)
         if (b->l2) {
             b->l2 = (char *) b->l2 + data_delta;
         }
+        if (b->l2_5) {
+            b->l2_5 = (char *) b->l2_5 + data_delta;
+        }
         if (b->l3) {
             b->l3 = (char *) b->l3 + data_delta;
         }
diff --git a/lib/ofpbuf.h b/lib/ofpbuf.h
index 520455d..bae3c0a 100644
--- a/lib/ofpbuf.h
+++ b/lib/ofpbuf.h
@@ -43,6 +43,7 @@ struct ofpbuf {
     size_t size;                /* Number of bytes in use. */
 
     void *l2;                   /* Link-level header. */
+    void *l2_5;                 /* MPLS label stack */
     void *l3;                   /* Network-level header. */
     void *l4;                   /* Transport-level header. */
     void *l7;                   /* Application data. */
diff --git a/lib/packets.c b/lib/packets.c
index 16f4fe6..c0f2142 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -20,6 +20,7 @@
 #include <arpa/inet.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
+#include <netinet/ip6.h>
 #include <stdlib.h>
 #include "byte-order.h"
 #include "csum.h"
@@ -211,6 +212,361 @@ eth_pop_vlan(struct ofpbuf *packet)
     }
 }
 
+/* Set ethertype of the packet. */
+void
+set_ethertype(struct ofpbuf *packet, ovs_be16 eth_type)
+{
+    struct eth_header *eh = packet->data;
+
+    if (eh->eth_type == htons(ETH_TYPE_VLAN)) {
+        /* ethtype for VLAN packets is at L3_offset - 2 bytes. */
+        ovs_be16 *next_ethtype;
+        next_ethtype = (ovs_be16 *)((char *)packet->l3 - 2);
+        *next_ethtype = eth_type;
+    } else {
+        eh->eth_type = eth_type;
+    }
+}
+
+/* Get ethertype of the packet. */
+static ovs_be16
+get_ethertype(struct ofpbuf *packet)
+{
+    struct eth_header *eh = packet->data;
+    char *mh = packet->l2_5;
+    ovs_be16 *ethtype = NULL;
+
+    if (eh->eth_type == htons(ETH_TYPE_VLAN)) {
+        if (mh != NULL) {
+            ethtype = (ovs_be16 *)(mh - 2);
+        } else {
+            ethtype = (ovs_be16 *)((char *)packet->l3 - 2);
+        }
+        return *ethtype;
+    } else {
+        return eh->eth_type;
+    }
+}
+
+/* Extract ttl and tos from ipv4 or ipv6 header
+   for non-IP pick default value. */
+static int
+get_label_ttl_and_tc(struct ofpbuf* packet, uint8_t *ttl,
+                     uint8_t *tc, uint8_t *label)
+{
+    struct eth_header *eh = packet->data;
+
+    if (packet->size < sizeof *eh) {
+        return -1;
+    }
+
+    switch (ntohs(get_ethertype(packet))) {
+    case ETH_TYPE_IP: {
+        struct ip_header *ih = packet->l3;
+
+        if (packet->size < sizeof *eh + sizeof *ih) {
+            return -1;
+        }
+        *ttl = ih->ip_ttl;
+        *tc = 0; /* As per OpenFlow 1.1 Spec. */
+        *label = 0; /* IPV4 Explicit null label. */
+        break;
+    }
+
+    case ETH_TYPE_IPV6: {
+        struct ip6_hdr   *ih6 = packet->l3;
+
+        if (packet->size < sizeof *eh + sizeof *ih6) {
+            return -1;
+        }
+        *ttl = ih6->ip6_hlim;
+        *tc = 0; /* As per OpenFlow 1.1 Spec. */
+        *label = 2; /* IPV6 Explicit null label. */
+        break;
+    }
+
+    case ETH_TYPE_MPLS:
+    case ETH_TYPE_MPLS_MCAST: {
+        struct mpls_hdr *mh = packet->l2_5;
+
+        if (packet->size < sizeof *eh + sizeof *mh) {
+            return -1;
+        }
+        *ttl = mpls_lse_to_ttl(mh->mpls_lse);
+        *tc = mpls_lse_to_tc(mh->mpls_lse);
+        *label = mpls_lse_to_label(mh->mpls_lse);
+        break;
+    }
+
+    default:
+        return -1;
+    }
+
+    return 0;
+}
+
+/* Set MPLS tag time-to-live. */
+static void
+set_mpls_lse_ttl(ovs_be32 *tag, ovs_be32 ttl)
+{
+    *tag &= ~htonl(MPLS_TTL_MASK);
+    *tag |= ttl & htonl(MPLS_TTL_MASK);
+}
+
+/* Set MPLS tag traffic-class. */
+static void
+set_mpls_lse_tc(ovs_be32 *tag, ovs_be32 tc)
+{
+    *tag &= ~htonl(MPLS_TC_MASK);
+    *tag |= tc & htonl(MPLS_TC_MASK);
+}
+
+/* Set MPLS tag label. */
+static void
+set_mpls_lse_label(ovs_be32 *tag, ovs_be32 label)
+{
+    *tag &= ~htonl(MPLS_LABEL_MASK);
+    *tag |= label & htonl(MPLS_LABEL_MASK);
+}
+
+/* Set MPLS tag stack. */
+static void
+set_mpls_lse_stack(ovs_be32 *tag, ovs_be32 stack)
+{
+    *tag &= ~htonl(MPLS_STACK_MASK);
+    *tag |= stack & htonl(MPLS_STACK_MASK);
+}
+
+/* Set MPLS lse from actions. */
+static void
+set_new_mpls_lse(struct mpls_hdr *mh, ovs_be32 mpls_lse)
+{
+    mh->mpls_lse = mpls_lse;
+}
+
+/* Set MPLS label, MPLS TC, MPLS ttl and MPLS stack. */
+static void
+set_mpls_lse_values(ovs_be32 *tag, uint8_t ttl, uint8_t stack,
+                    uint8_t tc, uint32_t label)
+{
+    set_mpls_lse_ttl(tag, htonl(ttl << MPLS_TTL_SHIFT));
+    set_mpls_lse_tc(tag, htonl(tc << MPLS_TC_SHIFT));
+    set_mpls_lse_label(tag, htonl(label << MPLS_LABEL_SHIFT));
+    set_mpls_lse_stack(tag, htonl(stack << MPLS_STACK_SHIFT));
+}
+
+/* Adjust L2 and L2.5 data after pushing new mpls shim header. */
+static void
+push_mpls_lse(struct ofpbuf *packet, struct mpls_hdr *mh)
+{
+    char * header;
+    size_t len;
+    header = ofpbuf_push_uninit(packet, MPLS_HLEN);
+    len = (char *)packet->l2_5 - (char *)packet->l2;
+    memmove(header, packet->l2, len);
+    memcpy(header + len, mh, sizeof *mh);
+    packet->l2 = (char*)packet->l2 - MPLS_HLEN;
+    packet->l2_5 = (char*)packet->l2_5 - MPLS_HLEN;
+}
+
+/* Decrement MPLS TTL from the packet.
+ * 'packet->l2_5' must initially point to 'packet''s MPLS Label stack. */
+void
+dec_mpls_ttl(struct ofpbuf *packet, uint8_t new_ttl)
+{
+    ovs_be16 eth_type = htons(0);
+    struct eth_header *eh = packet->data;
+    struct mpls_hdr *mh = packet->l2_5;
+
+    if (packet->size < sizeof *eh) {
+        return;
+    }
+
+    /* Packet type should be mpls to decrement ttl. */
+    eth_type = get_ethertype(packet);
+
+    if (eth_type == htons(ETH_TYPE_MPLS) ||
+        eth_type == htons(ETH_TYPE_MPLS_MCAST)) {
+
+        /* Update decremented ttl into mpls header. */
+        set_mpls_lse_ttl(&mh->mpls_lse, htonl(new_ttl << MPLS_TTL_SHIFT));
+    }
+}
+
+/* Copy MPLS TTL from the packet either ipv4/ipv6.
+ * 'packet->l2_5' must initially point to 'packet''s MPLS Label stack. */
+void
+copy_mpls_ttl_in(struct ofpbuf *packet, uint8_t new_ttl)
+{
+    struct eth_header *eh = packet->data;
+    struct mpls_hdr *mh = packet->l2_5;
+    struct ip_header *ih = packet->l3;
+    struct ip6_hdr *ih6 = packet->l3;
+    ovs_be16 eth_type = htons(0);
+    size_t hdr_size = sizeof *eh + sizeof *mh + sizeof *ih;
+
+    if (packet->size < hdr_size) {
+        return;
+    }
+
+    /* Packet type should be mpls to copy ttl to l3. */
+    eth_type = get_ethertype(packet);
+    if (eth_type == htons(ETH_TYPE_MPLS) ||
+        eth_type == htons(ETH_TYPE_MPLS_MCAST)) {
+
+        /* If bottom of the stack handle IP checksum. */
+        if (mh->mpls_lse & htonl(MPLS_STACK_MASK)) {
+            if (IP_VER(ih->ip_ihl_ver) == IP_VERSION) {
+                /* Change the ip checksum. */
+                uint8_t *field = &ih->ip_ttl;
+                ih->ip_csum = recalc_csum16(ih->ip_csum,
+                                 htons(*field << 8), htons(new_ttl << 8));
+                ih->ip_ttl = new_ttl;
+            } else if (IP6_VER(ih6->ip6_vfc) == IP6_VERSION) {
+                ih6->ip6_hlim = new_ttl;
+            }
+        } else {
+            struct mpls_hdr *mh2;
+            mh2 = (struct mpls_hdr *)((char *) packet->l2_5 + sizeof *mh);
+            set_mpls_lse_ttl(&mh2->mpls_lse, htonl(new_ttl << MPLS_TTL_SHIFT));
+        }
+    }
+}
+
+/* Copy MPLS TTL to the packet layer3 only ipv4/ipv6.
+ * 'packet->l2_5' must initially point to 'packet''s MPLS Label stack. */
+void
+copy_mpls_ttl_out(struct ofpbuf *packet, uint8_t new_ttl)
+{
+    struct eth_header *eh = packet->data;
+    struct mpls_hdr *mh = packet->l2_5;
+    struct ip_header *ih = packet->l3;
+    struct ip6_hdr   *ih6 = packet->l3;
+    ovs_be16 eth_type = htons(0);
+    size_t hdr_size = sizeof *eh + sizeof *mh + sizeof *ih;
+
+    /* TTL sent from ofproto-dpif.c is not the correct one,
+     * hence ignore it. */
+    if (packet->size < hdr_size) {
+        return;
+    }
+
+    /* Packet type should me mpls to copy ttl from l3. */
+    eth_type = get_ethertype(packet);
+    if (eth_type == htons(ETH_TYPE_MPLS) ||
+        eth_type == htons(ETH_TYPE_MPLS_MCAST)) {
+
+        /* If bottom of the stack copy from l3. */
+        if (mh->mpls_lse & htonl(MPLS_STACK_MASK)) {
+            uint8_t nh_ttl;
+            /* Get ipv4 or ipv6 or default ttl. */
+            if (IP_VER(ih->ip_ihl_ver) == IP_VERSION) {
+                nh_ttl = ih->ip_ttl;
+            } else if (IP6_VER(ih6->ip6_vfc) == IP6_VERSION) {
+                nh_ttl = ih6->ip6_hlim;
+            } else {
+                nh_ttl = 64; /* Default ttl for non-IP. */
+            }
+            set_mpls_lse_ttl(&mh->mpls_lse, htonl(nh_ttl << MPLS_TTL_SHIFT));
+        } else {
+            struct mpls_hdr *mh2;
+            mh2 = (struct mpls_hdr *)((char *) packet->l2_5 + sizeof *mh);
+            new_ttl = mpls_lse_to_ttl(mh2->mpls_lse);
+            set_mpls_lse_ttl(&mh->mpls_lse, htonl(new_ttl << MPLS_TTL_SHIFT));
+        }
+    }
+}
+
+/* Set MPLS label stack entry to outermost MPLS header.*/
+void
+set_mpls_lse(struct ofpbuf *packet, ovs_be32 mpls_lse)
+{
+    struct eth_header *eh = packet->data;
+    struct mpls_hdr *mh = packet->l2_5;
+    ovs_be16 eth_type = htons(0);
+
+    if (packet->size < sizeof *eh) {
+        return;
+    }
+
+    /* Packet type should me mpls to set label stack entry. */
+    eth_type = get_ethertype(packet);
+    if (eth_type == htons(ETH_TYPE_MPLS) ||
+        eth_type == htons(ETH_TYPE_MPLS_MCAST)) {
+        /* Update mpls label stack entry. */
+        set_new_mpls_lse(mh, mpls_lse);
+    }
+}
+
+/* Push MPLS label stack entry onto packet. */
+void
+push_mpls(struct ofpbuf *packet, ovs_be16 ethtype)
+{
+    struct eth_header *eh = packet->data;
+    uint8_t nh_ttl, mpls_tc, label;
+    ovs_be16 eth_type = htons(0);
+    struct mpls_hdr mh;
+
+    if (packet->size < sizeof *eh ||
+        (ethtype != htons(ETH_TYPE_MPLS) &&
+         ethtype != htons(ETH_TYPE_MPLS_MCAST))) {
+        return;
+    }
+    /* Get Label, time-to-live and tc from L3 or L2.5. */
+    if (get_label_ttl_and_tc(packet, &nh_ttl, &mpls_tc, &label)) {
+        return;
+    }
+
+    /* Get the packet ether_type. */
+    eth_type = get_ethertype(packet);
+
+    if (eth_type == htons(ETH_TYPE_MPLS) ||
+        eth_type == htons(ETH_TYPE_MPLS_MCAST)) {
+        set_mpls_lse_values(&mh.mpls_lse, nh_ttl, 0, mpls_tc, label);
+    } else {
+        /* Set ethtype and mpls label stack entry. */
+        set_ethertype(packet, ethtype);
+        set_mpls_lse_values(&mh.mpls_lse, nh_ttl, 1, mpls_tc, label);
+        packet->l2_5 = packet->l3;
+    }
+    /* Push new MPLS shim header onto packet. */
+    push_mpls_lse(packet, &mh);
+}
+
+/* Pop outermost MPLS label stack entry from packet. */
+void
+pop_mpls(struct ofpbuf *packet, ovs_be16 ethtype)
+{
+    struct eth_header *eh = packet->data;
+    struct mpls_hdr *mh = NULL;
+    ovs_be16 eth_type = htons(0);
+
+    if (packet->size < sizeof *eh + sizeof *mh)
+        return;
+
+    eth_type = get_ethertype(packet);
+
+    if (eth_type == htons(ETH_TYPE_MPLS) ||
+        eth_type == htons(ETH_TYPE_MPLS_MCAST)) {
+        size_t len;
+        mh = packet->l2_5;
+        len = (char*)packet->l2_5 - (char*)packet->l2;
+        /* If bottom of the stack set ethertype. */
+        if (mh->mpls_lse & htonl(MPLS_STACK_MASK)) {
+            packet->l3 = packet->l2_5;
+            packet->l2_5 = NULL;
+            set_ethertype(packet, ethtype);
+        } else {
+            packet->l2_5 = (char*)packet->l2_5 + MPLS_HLEN;
+        }
+        /* Shift the l2 header forward. */
+        memmove((char*)packet->data + MPLS_HLEN, packet->data, len);
+        packet->size -= MPLS_HLEN;
+        packet->data = (char*)packet->data + MPLS_HLEN;
+        packet->l2 = (char*)packet->l2 + MPLS_HLEN;
+    }
+}
+
 /* 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 24b51da..f41d3bb 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -140,6 +140,8 @@ void compose_rarp(struct ofpbuf *, const uint8_t eth_src[ETH_ADDR_LEN]);
 void eth_push_vlan(struct ofpbuf *, ovs_be16 tci);
 void eth_pop_vlan(struct ofpbuf *);
 
+void set_ethertype(struct ofpbuf *packet, ovs_be16 eth_type);
+
 const char *eth_from_hex(const char *hex, struct ofpbuf **packetp);
 void eth_format_masked(const uint8_t eth[ETH_ADDR_LEN],
                        const uint8_t mask[ETH_ADDR_LEN], struct ds *s);
@@ -147,6 +149,15 @@ void eth_addr_bitand(const uint8_t src[ETH_ADDR_LEN],
                      const uint8_t mask[ETH_ADDR_LEN],
                      uint8_t dst[ETH_ADDR_LEN]);
 
+void set_mpls_ttl(struct ofpbuf *, uint8_t ttl);
+void dec_mpls_ttl(struct ofpbuf *, uint8_t new_ttl);
+void copy_mpls_ttl_in(struct ofpbuf *, uint8_t new_ttl);
+void copy_mpls_ttl_out(struct ofpbuf *, uint8_t new_ttl);
+void set_mpls_tc(struct ofpbuf *, uint8_t tc);
+void set_mpls_lse(struct ofpbuf *, ovs_be32 label);
+void push_mpls(struct ofpbuf *packet, ovs_be16 ethtype);
+void pop_mpls(struct ofpbuf *, ovs_be16 ethtype);
+
 /* Example:
  *
  * uint8_t mac[ETH_ADDR_LEN];
@@ -270,6 +281,76 @@ struct vlan_eth_header {
 } __attribute__((packed));
 BUILD_ASSERT_DECL(VLAN_ETH_HEADER_LEN == sizeof(struct vlan_eth_header));
 
+/* MPLS related definitions */
+#define MPLS_TTL_MASK       0x000000ff
+#define MPLS_TTL_SHIFT      0
+
+#define MPLS_STACK_MASK     0x00000100
+#define MPLS_STACK_SHIFT    8
+
+#define MPLS_TC_MASK        0x00000e00
+#define MPLS_TC_SHIFT       9
+
+#define MPLS_LABEL_MASK     0xfffff000
+#define MPLS_LABEL_SHIFT    12
+
+#define MPLS_HLEN           4
+
+struct mpls_hdr {
+    ovs_be32 mpls_lse;
+};
+BUILD_ASSERT_DECL(MPLS_HLEN == sizeof(struct mpls_hdr));
+
+#define MPLS_ETH_HEADER_LEN (ETH_HEADER_LEN + MPLS_HLEN)
+struct mpls_eth_header {
+    uint8_t  eth_dst[ETH_ADDR_LEN];
+    uint8_t  eth_src[ETH_ADDR_LEN];
+    ovs_be16 eth_type;         /* htons(ETH_TYPE_MPLS) or
+                                  htons(ETH_TYPE_MPLS_MCAST). */
+    ovs_be32 mpls_lse;
+} __attribute__((packed));
+BUILD_ASSERT_DECL(MPLS_ETH_HEADER_LEN == sizeof(struct mpls_eth_header));
+
+/* Given a mpls label stack entry in network byte order
+ * return mpls label */
+static inline uint32_t
+mpls_lse_to_label(ovs_be32 mpls_lse)
+{
+    return (ntohl(mpls_lse) & MPLS_LABEL_MASK) >> MPLS_LABEL_SHIFT;
+}
+
+/* Given a mpls label stack entry in network byte order
+ * return mpls tc */
+static inline int
+mpls_lse_to_tc(ovs_be32 mpls_lse)
+{
+    return (ntohl(mpls_lse) & MPLS_TC_MASK) >> MPLS_TC_SHIFT;
+}
+
+/* Given a mpls label stack entry in network byte order
+ * return mpls ttl */
+static inline int
+mpls_lse_to_ttl(ovs_be32 mpls_lse)
+{
+    return (ntohl(mpls_lse) & MPLS_TTL_MASK) >> MPLS_TTL_SHIFT;
+}
+
+/* Set TTL in mpls lse. */
+static inline void
+flow_set_mpls_lse_ttl(ovs_be32 *mpls_lse, uint8_t ttl)
+{
+    *mpls_lse &= ~htonl(MPLS_TTL_MASK);
+    *mpls_lse |= htonl(ttl << MPLS_TTL_SHIFT);
+}
+
+/* Given a mpls label stack entry in network byte order
+ * return mpls stack */
+static inline int
+mpls_lse_to_stack(ovs_be32 mpls_lse)
+{
+    return (mpls_lse & htonl(MPLS_STACK_MASK)) != 0;
+}
+
 /* The "(void) (ip)[0]" below has no effect on the value, since it's the first
  * argument of a comma expression, but it makes sure that 'ip' is a pointer.
  * This is useful since a common mistake is to pass an integer instead of a
@@ -334,6 +415,8 @@ void ip_format_masked(ovs_be32 ip, ovs_be32 mask, struct ds *);
 
 #define IP_VERSION 4
 
+#define IP_DSCP(ip_tos) ((ip_tos & IP_DSCP_MASK) >> 2)
+
 #define IP_DONT_FRAGMENT  0x4000 /* Don't fragment. */
 #define IP_MORE_FRAGMENTS 0x2000 /* More fragments. */
 #define IP_FRAG_OFF_MASK  0x1fff /* Fragment offset. */
@@ -434,6 +517,11 @@ BUILD_ASSERT_DECL(ARP_ETH_HEADER_LEN == sizeof(struct arp_eth_header));
 /* The IPv6 flow label is in the lower 20 bits of the first 32-bit word. */
 #define IPV6_LABEL_MASK 0x000fffff
 
+#define IP6_VERSION    6
+
+#define IP6_VER(ip6_vfc) ((ip6_vfc) >> 4)
+#define IP6_TC(ip6_flow) ((ip6_flow >> 20) & 0xff)
+
 /* Example:
  *
  * char *string = "1 ::1 2";
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 9f7acd1..b045ffb 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -4951,6 +4951,7 @@ compose_output_action__(struct action_xlate_ctx *ctx, uint16_t ofp_port,
     const struct ofport_dpif *ofport = get_ofp_port(ctx->ofproto, ofp_port);
     uint16_t odp_port = ofp_port_to_odp_port(ofp_port);
     ovs_be16 flow_vlan_tci = ctx->flow.vlan_tci;
+    ovs_be32 flow_mpls_lse = ctx->flow.mpls_lse;
     uint8_t flow_nw_tos = ctx->flow.nw_tos;
     uint16_t out_port;
 
@@ -4985,6 +4986,7 @@ compose_output_action__(struct action_xlate_ctx *ctx, uint16_t ofp_port,
     ctx->sflow_n_outputs++;
     ctx->nf_output_iface = ofp_port;
     ctx->flow.vlan_tci = flow_vlan_tci;
+    ctx->flow.mpls_lse = flow_mpls_lse;
     ctx->flow.nw_tos = flow_nw_tos;
 }
 
@@ -5132,9 +5134,20 @@ execute_controller_action(struct action_xlate_ctx *ctx, int len,
 
         /* If the Ethernet type is less than ETH_TYPE_MIN, it's likely an 802.2
          * LLC frame.  Calculating the Ethernet type of these frames is more
-         * trouble than seems appropriate for a simple assertion. */
-        assert(ntohs(eh->eth_type) < ETH_TYPE_MIN
-               || eh->eth_type == ctx->flow.dl_type);
+         * trouble than seems appropriate for a simple assertion.
+         *
+         * There is a gratuitous exception for MPLS intended to handle
+         * the cases where an MPLS push or pull action has occurred and
+         * ctx->flow.dl_type will has been updated and the original
+         * value lost. This could be handled more cleanly, for instance by
+         * saving the original value of ctx->flow.dl_type when pushing the
+         * first MPLS label, but that seems somewhat excessive */
+        assert(ntohs(eh->eth_type) < ETH_TYPE_MIN ||
+               eh->eth_type == ctx->flow.dl_type ||
+               ctx->flow.dl_type == htons(ETH_TYPE_MPLS) ||
+               eh->eth_type == htons(ETH_TYPE_MPLS) ||
+               ctx->flow.dl_type == htons(ETH_TYPE_MPLS_MCAST) ||
+               eh->eth_type == htons(ETH_TYPE_MPLS_MCAST));
 
         memcpy(eh->eth_src, ctx->flow.dl_src, sizeof eh->eth_src);
         memcpy(eh->eth_dst, ctx->flow.dl_dst, sizeof eh->eth_dst);
@@ -5143,6 +5156,17 @@ execute_controller_action(struct action_xlate_ctx *ctx, int len,
             eth_push_vlan(packet, ctx->flow.vlan_tci);
         }
 
+        if (ctx->flow.mpls_lse) {
+            push_mpls(packet, ctx->flow.dl_type);
+            set_mpls_lse(packet, ctx->flow.mpls_lse);
+        }
+
+        if (!ctx->flow.mpls_lse &&
+            (eh->eth_type == htons(ETH_TYPE_MPLS) ||
+             eh->eth_type == htons(ETH_TYPE_MPLS_MCAST))) {
+            pop_mpls(packet, ctx->flow.dl_type);
+        }
+
         if (packet->l4) {
             if (ctx->flow.dl_type == htons(ETH_TYPE_IP)) {
                 packet_set_ipv4(packet, ctx->flow.nw_src, ctx->flow.nw_dst,
@@ -5175,6 +5199,77 @@ execute_controller_action(struct action_xlate_ctx *ctx, int len,
     ofpbuf_delete(packet);
 }
 
+/* Handle MPLS Push action. Update flow based on incoming packet. */
+static void
+commit_mpls_push_action(struct action_xlate_ctx *ctx, ovs_be16 eth_type)
+{
+    ovs_be32 mpls_label, mpls_tc, mpls_ttl, mpls_stack;
+
+    assert(eth_type == htons(ETH_TYPE_MPLS) ||
+           eth_type == htons(ETH_TYPE_MPLS_MCAST));
+
+    if (ctx->base_flow.mpls_lse != htonl(0)) {
+        ctx->flow.mpls_lse = ctx->base_flow.mpls_lse;
+        ctx->flow.mpls_lse &= ~htonl(MPLS_STACK_MASK);
+    } else {
+        ctx->flow.mpls_lse &= ~htonl(MPLS_LABEL_MASK | MPLS_TC_MASK |
+                                     MPLS_TTL_MASK | MPLS_STACK_MASK);
+        if (ctx->flow.dl_type == htons(ETH_TYPE_IPV6)) {
+            mpls_label = htonl(0x2); /* IPV6 Explicit Null. */
+        } else {
+            mpls_label = htonl(0x0); /* IPV4 Explicit Null. */
+        }
+        mpls_tc = htonl(((ctx->flow.nw_tos & IP_DSCP_MASK) >> 2)
+                        << MPLS_TC_SHIFT);
+        mpls_ttl = htonl(ctx->flow.nw_ttl << MPLS_TTL_SHIFT);
+        if (mpls_ttl == htonl(0)) {
+            mpls_ttl = htonl(0x40); /* Set default ttl for non-IP. */
+        }
+        mpls_stack = htonl(0x1 << MPLS_STACK_SHIFT);
+        ctx->flow.mpls_lse = mpls_label | mpls_tc | mpls_ttl | mpls_stack;
+        ctx->flow.encap_dl_type = ctx->base_flow.encap_dl_type =
+            ctx->flow.dl_type;
+    }
+
+    /* Update dl_type and mpls_lse fields. */
+    ctx->base_flow.dl_type = ctx->flow.dl_type = eth_type;
+    ctx->base_flow.mpls_lse = ctx->flow.mpls_lse;
+
+    compose_output_action(ctx, OFPP_LOCAL);
+}
+
+/* Handle MPLS Pop action. Update packet flow. */
+static void
+commit_mpls_pop_action(struct action_xlate_ctx *ctx, ovs_be16 eth_type)
+{
+    assert(ctx->flow.dl_type == htons(ETH_TYPE_MPLS) ||
+           ctx->flow.dl_type == htons(ETH_TYPE_MPLS_MCAST));
+    assert(eth_type != htons(ETH_TYPE_MPLS) &&
+           eth_type != htons(ETH_TYPE_MPLS_MCAST));
+    /* Update dl_type and mpls_lse fields. */
+    if (ctx->flow.mpls_lse & htonl(MPLS_STACK_MASK)) {
+        ctx->base_flow.dl_type = ctx->flow.dl_type = eth_type;
+        ctx->base_flow.encap_dl_type = ctx->flow.encap_dl_type = htons(0);
+    }
+    ctx->base_flow.mpls_lse = htonl(0);
+
+    compose_output_action(ctx, OFPP_LOCAL);
+}
+
+/* Handle MPLS Label Stack Entry action. Update packet flow. */
+static void
+commit_mpls_lse_action(struct action_xlate_ctx *ctx)
+{
+    if (ctx->base_flow.mpls_lse == ctx->flow.mpls_lse) {
+        return;
+    }
+
+    ctx->base_flow.mpls_lse = ctx->flow.mpls_lse;
+
+    compose_output_action(ctx, OFPP_LOCAL);
+}
+
+
 static bool
 compose_dec_ttl(struct action_xlate_ctx *ctx, struct ofpact_cnt_ids *ids)
 {
@@ -5200,6 +5295,79 @@ compose_dec_ttl(struct action_xlate_ctx *ctx, struct ofpact_cnt_ids *ids)
 }
 
 static void
+commit_dec_mpls_ttl_action(struct action_xlate_ctx *ctx)
+{
+    uint8_t ttl = mpls_lse_to_ttl(ctx->flow.mpls_lse);
+    if (ttl > 1) {
+        ttl--;
+        if (ctx->flow.mpls_lse != htonl(0)) {
+            flow_set_mpls_lse_ttl(&ctx->flow.mpls_lse, ttl);
+        }
+        if (ctx->base_flow.mpls_lse != htonl(0)) {
+            flow_set_mpls_lse_ttl(&ctx->base_flow.mpls_lse, ttl);
+        }
+        compose_output_action(ctx, OFPP_LOCAL);
+    } else {
+        execute_controller_action(ctx, UINT16_MAX, OFPR_INVALID_TTL, 0);
+    }
+}
+
+static void
+commit_copy_mpls_ttl_in_action(struct action_xlate_ctx *ctx)
+{
+    /* Get MPLS top label ttl to be copied. */
+    uint8_t ttl = 64;
+    if (ctx->flow.mpls_lse != htonl(0)) {
+        ttl = mpls_lse_to_ttl(ctx->flow.mpls_lse);;
+    } else if (ctx->base_flow.mpls_lse != htonl(0)) {
+        ttl = mpls_lse_to_ttl(ctx->base_flow.mpls_lse);;
+    }
+
+    /* Update ttl into flow inner mpls lse or l3 fields. */
+    if (ctx->base_flow.inner_mpls_lse != htonl(0)) {
+        flow_set_mpls_lse_ttl(&ctx->base_flow.inner_mpls_lse, ttl);
+    } else {
+        ctx->flow.nw_ttl = ttl;
+    }
+
+    if (ctx->flow.inner_mpls_lse != htonl(0)) {
+        flow_set_mpls_lse_ttl(&ctx->flow.inner_mpls_lse, ttl);
+    } else {
+        ctx->flow.nw_ttl = ttl;
+    }
+
+    compose_output_action(ctx, OFPP_LOCAL);
+}
+
+static void
+commit_copy_mpls_ttl_out_action(struct action_xlate_ctx *ctx)
+{
+    /* Packets in actins_xlate_ctx is NULL when pushing down flow stats,
+     * hence rely on kernel/userspace to modify ttl. */
+    uint8_t ttl = 64;
+
+    if (ctx->flow.inner_mpls_lse != htonl(0)) {
+        ttl = mpls_lse_to_ttl(ctx->flow.inner_mpls_lse);
+    } else if (ctx->flow.nw_ttl != 0) {
+        ttl = ctx->flow.nw_ttl;
+    } else if (ctx->base_flow.inner_mpls_lse != htonl(0)) {
+        ttl = mpls_lse_to_ttl(ctx->base_flow.inner_mpls_lse);
+    } else if (ctx->base_flow.nw_ttl != 0) {
+        ttl = ctx->base_flow.nw_ttl;
+    }
+
+    /* Update ttl into flow mpls lse. */
+    if (ctx->flow.mpls_lse != htonl(0)) {
+        flow_set_mpls_lse_ttl(&ctx->flow.mpls_lse, ttl);
+    }
+    if (ctx->base_flow.mpls_lse != htonl(0)) {
+        flow_set_mpls_lse_ttl(&ctx->base_flow.mpls_lse, ttl);
+    }
+
+    compose_output_action(ctx, OFPP_LOCAL);
+}
+
+static void
 xlate_output_action(struct action_xlate_ctx *ctx,
                     uint16_t port, uint16_t max_len, bool may_packet_in)
 {
@@ -5547,8 +5715,62 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             nxm_execute_reg_move(ofpact_get_REG_MOVE(a), &ctx->flow);
             break;
 
-        case OFPACT_REG_LOAD:
-            nxm_execute_reg_load(ofpact_get_REG_LOAD(a), &ctx->flow);
+        case OFPACT_REG_LOAD: {
+            struct ofpact_reg_load *load;
+            load = ofpact_get_REG_LOAD(a);
+            nxm_execute_reg_load(load, &ctx->flow);
+            if (load->dst.field->id == MFF_MPLS_LABEL ||
+                load->dst.field->id == MFF_MPLS_TC ||
+                load->dst.field->id == MFF_MPLS_STACK) {
+                commit_mpls_lse_action(ctx);
+            }
+            break;
+        }
+
+        case OFPACT_PUSH_MPLS:
+            commit_mpls_push_action(ctx, ofpact_get_PUSH_MPLS(a)->ethertype);
+            break;
+
+        case OFPACT_POP_MPLS:
+            commit_mpls_pop_action(ctx, ofpact_get_POP_MPLS(a)->ethertype);
+            ctx->flow.mpls_lse = htonl(0);
+            break;
+
+        case OFPACT_SET_MPLS_LABEL: {
+            ovs_be32 mpls_label = ofpact_get_SET_MPLS_LABEL(a)->mpls_label;
+            mpls_label = htonl(ntohl(mpls_label) << MPLS_LABEL_SHIFT);
+            ctx->flow.mpls_lse &= ~htonl(MPLS_LABEL_MASK);
+            ctx->flow.mpls_lse |= mpls_label;
+            commit_mpls_lse_action(ctx);
+            break;
+        }
+
+        case OFPACT_SET_MPLS_TC: {
+            uint32_t mpls_tc = ofpact_get_SET_MPLS_TC(a)->mpls_tc;
+            ctx->flow.mpls_lse &= ~htonl(MPLS_TC_MASK);
+            ctx->flow.mpls_lse |= htonl(mpls_tc << MPLS_TC_SHIFT);
+            commit_mpls_lse_action(ctx);
+            break;
+        }
+
+        case OFPACT_SET_MPLS_TTL: {
+            uint32_t mpls_ttl = ofpact_get_SET_MPLS_TTL(a)->mpls_ttl;
+            ctx->flow.mpls_lse &= ~htonl(MPLS_TTL_MASK);
+            ctx->flow.mpls_lse |= htonl(mpls_ttl << MPLS_TTL_SHIFT);
+            commit_mpls_lse_action(ctx);
+            break;
+        }
+
+        case OFPACT_DEC_MPLS_TTL:
+            commit_dec_mpls_ttl_action(ctx);
+            break;
+
+        case OFPACT_COPY_TTL_IN:
+            commit_copy_mpls_ttl_in_action(ctx);
+            break;
+
+        case OFPACT_COPY_TTL_OUT:
+            commit_copy_mpls_ttl_out_action(ctx);
             break;
 
         case OFPACT_DEC_TTL:
diff --git a/tests/automake.mk b/tests/automake.mk
index 20f9e82..c816741 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -101,6 +101,7 @@ valgrind_wrappers = \
 	tests/valgrind/test-csum \
 	tests/valgrind/test-file_name \
 	tests/valgrind/test-flows \
+	tests/valgrind/test-mpls \
 	tests/valgrind/test-hash \
 	tests/valgrind/test-heap \
 	tests/valgrind/test-hmap \
@@ -188,6 +189,10 @@ tests_test_flows_SOURCES = tests/test-flows.c
 tests_test_flows_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
 dist_check_SCRIPTS = tests/flowgen.pl
 
+noinst_PROGRAMS += tests/test-mpls
+tests_test_mpls_SOURCES = tests/test-mpls.c
+tests_test_mpls_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
+
 noinst_PROGRAMS += tests/test-hash
 tests_test_hash_SOURCES = tests/test-hash.c
 tests_test_hash_LDADD = lib/libopenvswitch.a
diff --git a/tests/odp.at b/tests/odp.at
index 9617af2..696e452 100644
--- a/tests/odp.at
+++ b/tests/odp.at
@@ -24,6 +24,11 @@ in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv
 in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0,proto=58,tclass=0,hlimit=128,frag=no),icmpv6(type=136,code=0),nd(target=::3,tll=00:0a:0b:0c:0d:0e)
 in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0,proto=58,tclass=0,hlimit=128,frag=no),icmpv6(type=136,code=0),nd(target=::3,sll=00:05:06:07:08:09,tll=00:0a:0b:0c:0d:0e)
 in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0806),arp(sip=1.2.3.4,tip=5.6.7.8,op=1,sha=00:0f:10:11:12:13,tha=00:14:15:16:17:18)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8847),mpls(label=100,tc=3,ttl=64,bos=1)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8847),mpls(label=100,tc=7,ttl=100,bos=1)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8847),mpls(label=100,tc=7,ttl=100,bos=0)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8848),mpls(label=1000,tc=4,ttl=200,bos=1)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8848),mpls(label=1000,tc=4,ttl=200,bos=0)
 ])
 
 (echo '# Valid forms without tun_id or VLAN header.'
@@ -39,6 +44,14 @@ in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0806),arp
 s/$/)/' odp-base.txt
 
  echo
+ echo '# Valid forms with MPLS header.'
+ sed 's/\(eth([[^)]]*),?\)/\1,eth_type(0x8847),mpls(label=100,tc=7,ttl=64,bos=1)/' odp-base.txt
+
+ echo
+ echo '# Valid forms with MPLS multicast header.'
+ sed 's/\(eth([[^)]]*),?\)/\1,eth_type(0x8848),mpls(label=100,tc=7,ttl=64,bos=1)/' odp-base.txt
+
+ echo
  echo '# Valid forms with QoS priority.'
  sed 's/^/priority(1234),/' odp-base.txt
 
@@ -61,8 +74,9 @@ sed -n 's/,frag=no),/,frag=first),/p' odp-base.txt
  echo
  echo '# Valid forms with IP later fragment.'
 sed -n 's/,frag=no),.*/,frag=later)/p' odp-base.txt) > odp.txt
+sed 's/\(.*mpls[(]l\)/ODP_FIT_TOO_LITTLE: \1/' odp.txt > odp-out.txt
 AT_CAPTURE_FILE([odp.txt])
-AT_CHECK_UNQUOTED([test-odp parse-keys < odp.txt], [0], [`cat odp.txt`
+AT_CHECK_UNQUOTED([test-odp parse-keys < odp.txt], [0], [`cat odp-out.txt`
 ])
 AT_CLEANUP
 
diff --git a/tests/ofp-print.at b/tests/ofp-print.at
index c2eb002..0dba8a8 100644
--- a/tests/ofp-print.at
+++ b/tests/ofp-print.at
@@ -337,7 +337,7 @@ c0 a8 00 02 27 2f 00 00 78 50 cc 5b 57 af 42 1e \
 50 00 02 00 26 e8 00 00 00 00 00 00 00 00 \
 "], [0], [dnl
 OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=3 (via no_match) data_len=60 buffer=0x00000111
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:06) type:0800 proto:6 tos:0 ttl:64 ip(192.168.0.1->192.168.0.2) port(10031->0) tcp_csum:26e8
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:06) type:0800,mpls(0) proto:6 tos:0 ttl:64 ip(192.168.0.1->192.168.0.2) port(10031->0) tcp_csum:26e8
 ])
 AT_CLEANUP
 
@@ -351,7 +351,7 @@ AT_CHECK([ovs-ofctl ofp-print "\
 00 00 00 23 20 83 c1 5f 00 00 00 00 \
 "], [0], [dnl
 OFPT_PACKET_IN (OF1.2) (xid=0x0): total_len=42 in_port=LOCAL (via no_match) data_len=42 buffer=0xffffff00
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(00:23:20:83:c1:5f->ff:ff:ff:ff:ff:ff) type:8035
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(00:23:20:83:c1:5f->ff:ff:ff:ff:ff:ff) type:8035,mpls(0)
 ])
 AT_CLEANUP
 
@@ -1154,7 +1154,7 @@ ff ff ff ff ff ff 00 00 00 00 82 82 82 82 82 82 \
 31 6d 00 00 00 00 00 00 00 00 \
 "], [0], [dnl
 NXT_PACKET_IN (xid=0x0): table_id=7 cookie=0x9 total_len=64 in_port=1 tun_id=0x6 metadata=0x5a5a5a5a5a5a5a5a reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 (via action) data_len=64 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) tcp_csum:316d
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) tcp_csum:316d
 ])
 AT_CLEANUP
 
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index de56ef8..16048e5 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -98,7 +98,7 @@ AT_CHECK([ovs-appctl ofproto/trace br0 'in_port(1),eth(src=50:54:00:00:00:05,dst
 OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
 AT_CHECK([cat ofctl_monitor.log], [0], [dnl
 NXT_PACKET_IN (xid=0x0): table_id=1 total_len=42 in_port=1 (via invalid_ttl) data_len=42 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800 proto:1 tos:0 ttl:1 ip(192.168.0.1->192.168.0.2)
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800,mpls(0) proto:1 tos:0 ttl:1 ip(192.168.0.1->192.168.0.2)
 ])
 OVS_VSWITCHD_STOP
 AT_CLEANUP
@@ -250,6 +250,12 @@ cookie=0x6 table=4 in_port=83 actions=load:4->NXM_NX_REG3[[]],mod_nw_src:83.83.8
 cookie=0x7 table=5 in_port=84 actions=load:5->NXM_NX_REG4[[]],load:6->NXM_NX_TUN_ID[[]],mod_nw_dst:84.84.84.84,controller,resubmit(85,6)
 cookie=0x8 table=6 in_port=85 actions=mod_tp_src:85,controller,resubmit(86,7)
 cookie=0x9 table=7 in_port=86 actions=mod_tp_dst:86,controller,controller
+cookie=0xa dl_src=40:44:44:44:44:44 actions=push_mpls:0x8847,set_mpls_label:10,set_mpls_tc:3,set_mpls_ttl:64,dec_mpls_ttl,controller
+cookie=0xb dl_src=50:55:55:55:55:55 actions=set_mpls_label:1000,set_mpls_ttl:200,copy_ttl_out,controller
+cookie=0xb dl_src=50:55:55:55:55:56 actions=set_mpls_label:1000,set_mpls_ttl:200,controller
+cookie=0xd dl_src=60:66:66:66:66:66 actions=pop_mpls:0x0800,controller
+cookie=0xc dl_src=70:77:77:77:77:77 actions=push_mpls:0x8848,set_mpls_label:1000,set_mpls_tc:7,set_mpls_ttl:250,controller
+cookie=0xc dl_src=70:77:77:77:77:78 actions=push_mpls:0x8848,set_mpls_label:1000,set_mpls_tc:7,set_mpls_ttl:250,copy_ttl_out,controller
 ])
 AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
 
@@ -263,13 +269,13 @@ done
 OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
 AT_CHECK([cat ofctl_monitor.log], [0], [dnl
 OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=1 (via no_match) data_len=60 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->9) tcp_csum:0
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->9) tcp_csum:0
 dnl
 OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=1 (via no_match) data_len=60 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->9) tcp_csum:0
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->9) tcp_csum:0
 dnl
 OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=1 (via no_match) data_len=60 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->9) tcp_csum:0
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->9) tcp_csum:0
 ])
 
 dnl Singleton controller action.
@@ -282,13 +288,13 @@ done
 OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
 AT_CHECK([cat ofctl_monitor.log], [0], [dnl
 OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=1 (via action) data_len=60 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(10:11:11:11:11:11->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(10:11:11:11:11:11->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
 dnl
 OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=1 (via action) data_len=60 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(10:11:11:11:11:11->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(10:11:11:11:11:11->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
 dnl
 OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=1 (via action) data_len=60 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(10:11:11:11:11:11->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(10:11:11:11:11:11->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
 ])
 
 dnl Modified controller action.
@@ -301,13 +307,128 @@ done
 OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
 AT_CHECK([cat ofctl_monitor.log], [0], [dnl
 OFPT_PACKET_IN (xid=0x0): total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:15,pcp:0) mac(30:33:33:33:33:33->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:15,pcp:0) mac(30:33:33:33:33:33->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
 dnl
 OFPT_PACKET_IN (xid=0x0): total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:15,pcp:0) mac(30:33:33:33:33:33->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:15,pcp:0) mac(30:33:33:33:33:33->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
 dnl
 OFPT_PACKET_IN (xid=0x0): total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:15,pcp:0) mac(30:33:33:33:33:33->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:15,pcp:0) mac(30:33:33:33:33:33->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
+])
+
+dnl Modified MPLS controller action.
+AT_CHECK([ovs-ofctl monitor br0 65534 -P nxm --detach --pidfile 2> ofctl_monitor.log])
+
+for i in 1 2 3; do
+    ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=40:44:44:44:44:44,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no)'
+done
+
+OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
+AT_CHECK([cat ofctl_monitor.log], [0], [dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xa total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(40:44:44:44:44:44->50:54:00:00:00:07) type:8847,mpls(label:10,tc:3,ttl:63,bos:1)
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xa total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(40:44:44:44:44:44->50:54:00:00:00:07) type:8847,mpls(label:10,tc:3,ttl:63,bos:1)
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xa total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(40:44:44:44:44:44->50:54:00:00:00:07) type:8847,mpls(label:10,tc:3,ttl:63,bos:1)
+])
+
+dnl Modified MPLS actions.
+AT_CHECK([ovs-ofctl monitor br0 65534 -P nxm --detach --pidfile 2> ofctl_monitor.log])
+
+for i in 1 2 3; do
+    ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:55:55:55:55:55,dst=50:54:00:00:00:07),eth_type(0x8847),mpls(label=100,tc=7,ttl=64,bos=1),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no)'
+done
+
+OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
+AT_CHECK([cat ofctl_monitor.log], [0], [dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xb total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(50:55:55:55:55:55->50:54:00:00:00:07) type:8847,mpls(label:1000,tc:7,ttl:64,bos:1)
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xb total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(50:55:55:55:55:55->50:54:00:00:00:07) type:8847,mpls(label:1000,tc:7,ttl:64,bos:1)
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xb total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(50:55:55:55:55:55->50:54:00:00:00:07) type:8847,mpls(label:1000,tc:7,ttl:64,bos:1)
+])
+
+dnl Modified MPLS actions and copy MPLS ttl.
+AT_CHECK([ovs-ofctl monitor br0 65534 -P nxm --detach --pidfile 2> ofctl_monitor.log])
+
+for i in 1 2 3; do
+    ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:55:55:55:55:56,dst=50:54:00:00:00:07),eth_type(0x8847),mpls(label=100,tc=7,ttl=64,bos=1),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no)'
+done
+
+OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
+AT_CHECK([cat ofctl_monitor.log], [0], [dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xb total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(50:55:55:55:55:56->50:54:00:00:00:07) type:8847,mpls(label:1000,tc:7,ttl:200,bos:1)
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xb total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(50:55:55:55:55:56->50:54:00:00:00:07) type:8847,mpls(label:1000,tc:7,ttl:200,bos:1)
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xb total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(50:55:55:55:55:56->50:54:00:00:00:07) type:8847,mpls(label:1000,tc:7,ttl:200,bos:1)
+])
+
+dnl Modified MPLS ipv6 controller action.
+AT_CHECK([ovs-ofctl monitor br0 65534 -P nxm --detach --pidfile 2> ofctl_monitor.log])
+
+for i in 1 2 3; do
+    ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=70:77:77:77:77:77,dst=50:54:00:00:00:07),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0,proto=10,tclass=0x70,hlimit=128,frag=no)'
+done
+
+OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
+AT_CHECK([cat ofctl_monitor.log], [0], [dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xc total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(70:77:77:77:77:77->50:54:00:00:00:07) type:8848,mpls(label:1000,tc:7,ttl:250,bos:1)
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xc total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(70:77:77:77:77:77->50:54:00:00:00:07) type:8848,mpls(label:1000,tc:7,ttl:250,bos:1)
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xc total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(70:77:77:77:77:77->50:54:00:00:00:07) type:8848,mpls(label:1000,tc:7,ttl:250,bos:1)
+])
+
+
+dnl Modified MPLS ipv6 controller with copy ttl action.
+AT_CHECK([ovs-ofctl monitor br0 65534 -P nxm --detach --pidfile 2> ofctl_monitor.log])
+
+for i in 1 2 3; do
+    ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=70:77:77:77:77:78,dst=50:54:00:00:00:07),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0,proto=10,tclass=0x70,hlimit=128,frag=no)'
+done
+
+OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
+AT_CHECK([cat ofctl_monitor.log], [0], [dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xc total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(70:77:77:77:77:78->50:54:00:00:00:07) type:8848,mpls(label:1000,tc:7,ttl:64,bos:1)
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xc total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(70:77:77:77:77:78->50:54:00:00:00:07) type:8848,mpls(label:1000,tc:7,ttl:64,bos:1)
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xc total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(70:77:77:77:77:78->50:54:00:00:00:07) type:8848,mpls(label:1000,tc:7,ttl:64,bos:1)
+])
+
+dnl Modified MPLS pop action.
+AT_CHECK([ovs-ofctl monitor br0 65534 -P nxm --detach --pidfile 2> ofctl_monitor.log])
+
+for i in 1 2 3; do
+    ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=60:66:66:66:66:66,dst=50:54:00:00:00:07),eth_type(0x8847),mpls(label=10,tc=3,ttl=100,bos=1),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no)'
+done
+
+OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
+AT_CHECK([cat ofctl_monitor.log], [0], [dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=56 in_port=1 (via action) data_len=56 (unbuffered)
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(60:66:66:66:66:66->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) tcp_csum:0
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=56 in_port=1 (via action) data_len=56 (unbuffered)
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(60:66:66:66:66:66->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) tcp_csum:0
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=56 in_port=1 (via action) data_len=56 (unbuffered)
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(60:66:66:66:66:66->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) tcp_csum:0
 ])
 
 dnl Checksum TCP.
@@ -320,31 +441,31 @@ done
 OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
 AT_CHECK([cat ofctl_monitor.log], [0], [dnl
 NXT_PACKET_IN (xid=0x0): cookie=0x1 total_len=60 in_port=1 (via action) data_len=60 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
 dnl
 NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x3 total_len=64 in_port=1 reg0=0x1 (via action) data_len=64 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
 dnl
 NXT_PACKET_IN (xid=0x0): table_id=2 cookie=0x4 total_len=64 in_port=1 reg0=0x1 reg1=0x2 (via action) data_len=64 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
 dnl
 NXT_PACKET_IN (xid=0x0): table_id=3 cookie=0x5 total_len=64 in_port=1 reg0=0x1 reg1=0x2 reg2=0x3 (via action) data_len=64 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
 dnl
 NXT_PACKET_IN (xid=0x0): table_id=4 cookie=0x6 total_len=64 in_port=1 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 (via action) data_len=64 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:6 tos:0 ttl:0 ip(83.83.83.83->192.168.0.2) port(8->11) tcp_csum:1a03
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->192.168.0.2) port(8->11) tcp_csum:1a03
 dnl
 NXT_PACKET_IN (xid=0x0): table_id=5 cookie=0x7 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 (via action) data_len=64 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(8->11) tcp_csum:3205
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(8->11) tcp_csum:3205
 dnl
 NXT_PACKET_IN (xid=0x0): table_id=6 cookie=0x8 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 (via action) data_len=64 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->11) tcp_csum:31b8
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->11) tcp_csum:31b8
 dnl
 NXT_PACKET_IN (xid=0x0): table_id=7 cookie=0x9 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 (via action) data_len=64 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) tcp_csum:316d
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) tcp_csum:316d
 dnl
 NXT_PACKET_IN (xid=0x0): table_id=7 cookie=0x9 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 (via action) data_len=64 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) tcp_csum:316d
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) tcp_csum:316d
 ])
 
 dnl Checksum UDP.
@@ -357,31 +478,31 @@ done
 OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
 AT_CHECK([cat ofctl_monitor.log], [0], [dnl
 NXT_PACKET_IN (xid=0x0): cookie=0x1 total_len=60 in_port=1 (via action) data_len=60 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800 proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
 dnl
 NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x3 total_len=64 in_port=1 reg0=0x1 (via action) data_len=64 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800 proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
 dnl
 NXT_PACKET_IN (xid=0x0): table_id=2 cookie=0x4 total_len=64 in_port=1 reg0=0x1 reg1=0x2 (via action) data_len=64 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->50:54:00:00:00:07) type:0800 proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->50:54:00:00:00:07) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
 dnl
 NXT_PACKET_IN (xid=0x0): table_id=3 cookie=0x5 total_len=64 in_port=1 reg0=0x1 reg1=0x2 reg2=0x3 (via action) data_len=64 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
 dnl
 NXT_PACKET_IN (xid=0x0): table_id=4 cookie=0x6 total_len=64 in_port=1 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 (via action) data_len=64 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:17 tos:0 ttl:0 ip(83.83.83.83->192.168.0.2) port(8->11) udp_csum:2c37
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(83.83.83.83->192.168.0.2) port(8->11) udp_csum:2c37
 dnl
 NXT_PACKET_IN (xid=0x0): table_id=5 cookie=0x7 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 (via action) data_len=64 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(8->11) udp_csum:4439
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(8->11) udp_csum:4439
 dnl
 NXT_PACKET_IN (xid=0x0): table_id=6 cookie=0x8 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 (via action) data_len=64 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->11) udp_csum:43ec
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->11) udp_csum:43ec
 dnl
 NXT_PACKET_IN (xid=0x0): table_id=7 cookie=0x9 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 (via action) data_len=64 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) udp_csum:43a1
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) udp_csum:43a1
 dnl
 NXT_PACKET_IN (xid=0x0): table_id=7 cookie=0x9 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 (via action) data_len=64 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) udp_csum:43a1
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) udp_csum:43a1
 ])
 
 AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
@@ -394,6 +515,12 @@ AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
  cookie=0x7, table=5, n_packets=2, n_bytes=120, in_port=84 actions=load:0x5->NXM_NX_REG4[[]],load:0x6->NXM_NX_TUN_ID[[]],mod_nw_dst:84.84.84.84,CONTROLLER:65535,resubmit(85,6)
  cookie=0x8, table=6, n_packets=2, n_bytes=120, in_port=85 actions=mod_tp_src:85,CONTROLLER:65535,resubmit(86,7)
  cookie=0x9, table=7, n_packets=2, n_bytes=120, in_port=86 actions=mod_tp_dst:86,CONTROLLER:65535,CONTROLLER:65535
+ cookie=0xa, n_packets=3, n_bytes=180, dl_src=40:44:44:44:44:44 actions=push_mpls:0x8847,set_mpls_label:10,set_mpls_tc:3,set_mpls_ttl:64,dec_mpls_ttl,CONTROLLER:65535
+ cookie=0xb, n_packets=3, n_bytes=180, dl_src=50:55:55:55:55:55 actions=set_mpls_label:1000,set_mpls_ttl:200,copy_ttl_out,CONTROLLER:65535
+ cookie=0xb, n_packets=3, n_bytes=180, dl_src=50:55:55:55:55:56 actions=set_mpls_label:1000,set_mpls_ttl:200,CONTROLLER:65535
+ cookie=0xc, n_packets=3, n_bytes=180, dl_src=70:77:77:77:77:77 actions=push_mpls:0x8848,set_mpls_label:1000,set_mpls_tc:7,set_mpls_ttl:250,CONTROLLER:65535
+ cookie=0xc, n_packets=3, n_bytes=180, dl_src=70:77:77:77:77:78 actions=push_mpls:0x8848,set_mpls_label:1000,set_mpls_tc:7,set_mpls_ttl:250,copy_ttl_out,CONTROLLER:65535
+ cookie=0xd, n_packets=3, n_bytes=180, dl_src=60:66:66:66:66:66 actions=pop_mpls:0x0800,CONTROLLER:65535
  n_packets=3, n_bytes=180, dl_src=10:11:11:11:11:11 actions=CONTROLLER:65535
 NXST_FLOW reply:
 ])
diff --git a/tests/ofproto.at b/tests/ofproto.at
index af7b7ca..895369f 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -588,21 +588,21 @@ check_async () {
     ovs-ofctl -v packet-out br0 none controller '0001020304050010203040501234'
     if test X"$1" = X"OFPR_ACTION"; then shift;
         echo >>expout "OFPT_PACKET_IN: total_len=14 in_port=NONE (via action) data_len=14 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:1234"
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:1234,mpls(0)"
     fi
 
     # OFPT_PACKET_IN, OFPR_NO_MATCH (controller_id=123)
     ovs-ofctl -v packet-out br0 none 'controller(reason=no_match,id=123)' '0001020304050010203040501234'
     if test X"$1" = X"OFPR_NO_MATCH"; then shift;
         echo >>expout "OFPT_PACKET_IN: total_len=14 in_port=NONE (via no_match) data_len=14 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:1234"
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:1234,mpls(0)"
     fi
 
     # OFPT_PACKET_IN, OFPR_INVALID_TTL (controller_id=0)
     ovs-ofctl packet-out br0 none dec_ttl '002583dfb4000026b98cb0f908004500003fb7e200000011339bac11370dac100002d7730035002b8f6d86fb0100000100000000000006626c702d7873066e696369726103636f6d00000f00'
     if test X"$1" = X"OFPR_INVALID_TTL"; then shift;
         echo >>expout "OFPT_PACKET_IN: total_len=76 in_port=NONE (via invalid_ttl) data_len=76 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(00:26:b9:8c:b0:f9->00:25:83:df:b4:00) type:0800 proto:17 tos:0 ttl:0 ip(172.17.55.13->172.16.0.2) port(55155->53) udp_csum:8f6d"
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(00:26:b9:8c:b0:f9->00:25:83:df:b4:00) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(172.17.55.13->172.16.0.2) port(55155->53) udp_csum:8f6d"
     fi
 
     # OFPT_PORT_STATUS, OFPPR_ADD
@@ -700,9 +700,9 @@ ovs-appctl -t ovs-ofctl exit
 
 AT_CHECK([sed 's/ (xid=0x[[0-9a-fA-F]]*)//' monitor.log], [0], [dnl
 OFPT_PACKET_IN: total_len=14 in_port=NONE (via action) data_len=14 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:1234
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:1234,mpls(0)
 OFPT_PACKET_IN: total_len=14 in_port=CONTROLLER (via action) data_len=14 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:5678
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:5678,mpls(0)
 OFPT_BARRIER_REPLY:
 ])
 
@@ -730,7 +730,7 @@ ovs-appctl -t ovs-ofctl exit
 
 AT_CHECK([sed 's/ (xid=0x[[0-9a-fA-F]]*)//' monitor.log], [0], [dnl
 NXT_PACKET_IN: total_len=14 in_port=NONE metadata=0xfafafafa5a5a5a5a (via action) data_len=14 (unbuffered)
-priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:1234
+priority:0,tunnel:0,metadata:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:1234,mpls(0)
 OFPT_BARRIER_REPLY:
 ])
 
diff --git a/tests/test-bundle.c b/tests/test-bundle.c
index aa8b6f0..bd6b778 100644
--- a/tests/test-bundle.c
+++ b/tests/test-bundle.c
@@ -136,7 +136,7 @@ main(int argc, char *argv[])
     flows = xmalloc(N_FLOWS * sizeof *flows);
     for (i = 0; i < N_FLOWS; i++) {
         random_bytes(&flows[i], sizeof flows[i]);
-        memset(flows[i].zeros, 0, sizeof flows[i].zeros);
+        flow_zero_pad(flows + i);
         flows[i].regs[0] = OFPP_NONE;
     }
 
diff --git a/tests/test-mpls.c b/tests/test-mpls.c
new file mode 100644
index 0000000..05f23f1
--- /dev/null
+++ b/tests/test-mpls.c
@@ -0,0 +1,288 @@
+/*
+ * Copyright (c) 2012 Nicira, 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <features.h>
+#ifndef __aligned_u64
+#define __aligned_u64 __u64 __attribute__((aligned(8)))
+#endif
+#include <linux/if_packet.h>
+#include <linux/if_ether.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include "csum.h"
+#include "packets.h"
+
+#define PKT_LENGTH	     512
+#define ETH_DST_ADDR_OFF     0
+#define ETH_SRC_ADDR_OFF     ETH_DST_ADDR_OFF  + ETH_ALEN
+#define ETH_TYPE_ADDR_OFF    ETH_SRC_ADDR_OFF  + ETH_ALEN
+#define VLAN_TPID_ADDR_OFF   ETH_TYPE_ADDR_OFF
+#define VLAN_VID_ADDR_OFF    VLAN_TPID_ADDR_OFF + 2
+#define VLAN_TYPE_ADDR_OFF   VLAN_VID_ADDR_OFF + 2
+#define MPLS_HDR_ADDR_OFF    ETH_TYPE_ADDR_OFF + 2
+#define IP_HDR_ADDR_OFF      MPLS_HDR_ADDR_OFF + 4
+
+struct vlan_hdr {
+    uint16_t value;
+};
+
+static int
+create_sock (int proto)
+{
+    int sock_fd;
+
+    if ((sock_fd = socket(AF_PACKET, SOCK_RAW, proto)) == -1) {
+        perror("Error creating socket: ");
+        exit(-1);
+    }
+    return sock_fd;
+}
+
+static int
+bind_sock (char *device, int sock_fd, int protocol)
+{
+
+    struct sockaddr_ll sll;
+    struct ifreq ifr;
+    bzero(&sll, sizeof(sll));
+    bzero(&ifr, sizeof(ifr));
+
+    /* First Get the Interface Index  */
+    strncpy((char *)ifr.ifr_name, device, IFNAMSIZ);
+    if ((ioctl(sock_fd, SIOCGIFINDEX, &ifr)) == -1) {
+        printf("Error getting Interface index !\n");
+        exit(-1);
+    }
+
+    /* Bind socket to this interface */
+    sll.sll_family = AF_PACKET;
+    sll.sll_ifindex = ifr.ifr_ifindex;
+    sll.sll_protocol = htons(protocol);
+
+    if ((bind(sock_fd, (struct sockaddr *)&sll, sizeof(sll)))== -1) {
+        perror("Error binding socket to interface\n");
+        exit(-1);
+    }
+
+    return 1;
+}
+
+static int
+send_pkt (int sock_fd, uint8_t *pkt, int pkt_len)
+{
+    int sent = 0;
+
+    /* A simple write on the socket ..thats all it takes ! */
+
+    if ((sent = write(sock_fd, pkt, pkt_len)) != pkt_len) {
+        return 0;
+    }
+    return 1;
+}
+
+static void
+write_ether_type (uint8_t *pkt, uint16_t eth_type)
+{
+    ovs_be16 tmp_eth_type;
+    tmp_eth_type = htons(eth_type);
+    memcpy((void*)pkt, (void*)&tmp_eth_type, 2);
+}
+
+static void
+write_ether_hdr (uint8_t *pkt, uint16_t eth_type)
+{
+    ovs_be16 tmp_eth_type;
+    /*MAC address of the host*/
+    uint8_t src_mac[ETH_ALEN] = {0x00, 0x27, 0x13, 0x67, 0xb9, 0x9b};
+
+    /*gateway MAC address*/
+    uint8_t dest_mac[ETH_ALEN] = {0x00, 0x1f, 0x9e, 0x2a, 0x7f, 0xdd};
+
+    tmp_eth_type = htons(eth_type);
+
+    memcpy((void*)(pkt + ETH_DST_ADDR_OFF), (void*)dest_mac, ETH_ALEN);
+    memcpy((void*)(pkt + ETH_SRC_ADDR_OFF), (void*)src_mac, ETH_ALEN);
+    memcpy((void*)(pkt + ETH_TYPE_ADDR_OFF), (void*)&tmp_eth_type, 2);
+}
+
+static void
+write_vlan_hdr (uint8_t *pkt, uint16_t vid, uint16_t pcp, uint16_t id)
+{
+    struct vlan_hdr vlan_h;
+    ovs_be16 vlan_raw;
+    ovs_be16 tpid = htons(id);
+
+    vlan_h.value = ((vid << VLAN_VID_SHIFT) & VLAN_VID_MASK) |
+                   ((pcp << VLAN_PCP_SHIFT) & VLAN_PCP_MASK);
+
+    vlan_raw = htons(vlan_h.value);
+
+    memcpy((void*)pkt, (void *)&tpid, 2);
+    memcpy((void*)(pkt+2), (void *) &vlan_raw, 2);
+}
+
+static void
+write_mpls_hdr (uint8_t *pkt, uint32_t label,
+                uint32_t tc, uint32_t s, uint32_t ttl)
+{
+    struct mpls_hdr mpls_h;
+
+    mpls_h.mpls_lse = htonl(((ttl << MPLS_TTL_SHIFT) &  MPLS_TTL_MASK)  |
+                            ((tc << MPLS_TC_SHIFT) & MPLS_TC_MASK)      |
+                            ((s << MPLS_STACK_SHIFT) & MPLS_STACK_MASK) |
+                            ((label << MPLS_LABEL_SHIFT) & MPLS_LABEL_MASK));
+
+    memcpy((void*)(pkt), (void *) &mpls_h.mpls_lse, 4);
+}
+
+static void
+write_ip_hdr (uint8_t *pkt, uint16_t ip_pkt_len)
+{
+     uint8_t ip_hdr[20] = { 0x45, 0x07, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x00,
+                            0x10, 0x11, 0xa3, 0xfc,
+                            0x0a, 0x75, 0x2e, 0xc8,
+                            0x0a, 0x75, 0x2e, 0xc1};
+
+    ip_hdr[2] = (0xFF00 & ip_pkt_len) >> 8;
+    ip_hdr[3] = 0x00FF & ip_pkt_len;
+
+    memcpy((void *)(pkt), (void *) &ip_hdr, 20);
+}
+
+static void
+write_udp_hdr (uint8_t *pkt, uint16_t udp_len)
+{
+    uint8_t udp_hdr[8] = {0x0F, 0x00, 0x0F, 0x00,
+                          0x00, 0x00, 0x00, 0x00};
+    udp_hdr[4] = (0xFF00 & udp_len) >> 8;
+    udp_hdr[5] = (0x00FF & udp_len);
+    memcpy((void *)(pkt), (void *) &udp_hdr, 8);
+}
+
+static void
+write_ip_csum (uint8_t *pkt, uint16_t len)
+{
+    /* len should be just the length of the header */
+    ovs_be16 ip_csum = 0;
+
+    /* initialize the ip checksum field to 0 for
+     * purposes of calculating the header */
+    memcpy(pkt + 10, &ip_csum, 2);
+
+    /* appears to return in network byte order somehow */
+    ip_csum = csum(pkt, len);
+    memcpy(pkt + 10, &ip_csum, 2);
+}
+
+/* argv[1] is the device e.g. eth0
+   argv[2] is the number of pkts to send
+*/
+int
+main (int argc, char **argv)
+{
+
+    int sock_fd;
+    uint8_t pkt[PKT_LENGTH];
+    uint8_t *pkt_pos = pkt;
+    uint8_t *ip_pos;
+    uint32_t label = 101, tc = 4, ttl = 10;
+    uint16_t vid = 101, pcp = 4;
+    uint32_t num_of_pkts, num_labels;
+    uint16_t i = 0;
+    char *str = "FEEDFACE", type[5];
+
+    if (argc != 5) {
+        printf("usage: %s <device> <# pkts> <#labels> <type=vlan/mpls>\n", argv[0]);
+        return -1;
+    }
+
+    num_of_pkts = atoi(argv[2]);
+
+    strncpy(type, argv[argc-1], 5);
+
+    /* Set the magic data 0xfeedface */
+    for (i = 0; i < PKT_LENGTH; i+=8) {
+        memcpy((void*)(pkt + i), (void*)str, 8);
+    }
+
+    num_labels = atoi(argv[3]);
+
+    if (!strcmp(type, "vlan")) {
+        write_ether_hdr(pkt_pos, ETH_TYPE_IP);
+        pkt_pos += ETH_TYPE_ADDR_OFF;
+        for (i = 0; i < num_labels; i++) {
+            if (i == 1 || num_labels == 1) {
+                write_vlan_hdr(pkt_pos, vid++, pcp++, ETH_TYPE_VLAN);
+            }
+            else {
+                write_vlan_hdr(pkt_pos, vid++, pcp++, ETH_TYPE_VLAN);
+            }
+            pkt_pos += 4;
+        }
+        write_ether_type(pkt_pos, ETH_TYPE_IP);
+        pkt_pos+=2;
+    } else {
+        write_ether_hdr(pkt_pos, ETH_TYPE_MPLS);
+        pkt_pos += MPLS_HDR_ADDR_OFF;
+        for (i = 0; i < num_labels; i++) {
+            if (i == num_labels - 1) {
+                write_mpls_hdr(pkt_pos, label++, tc, 1, ttl++);
+            } else {
+                write_mpls_hdr(pkt_pos, label++, tc, 0, ttl++);
+            }
+            pkt_pos += 4;
+        }
+    }
+
+    ip_pos = pkt_pos;
+    write_ip_hdr(pkt_pos, PKT_LENGTH - (ip_pos - pkt));
+    pkt_pos += 20;
+
+    write_udp_hdr(pkt_pos, PKT_LENGTH -(pkt_pos - pkt));
+    pkt_pos += 8;
+
+    write_ip_csum(ip_pos, 20);
+
+    /* Create the socket */
+    sock_fd = create_sock(ETH_P_ALL);
+
+    /* Bind socket to interface */
+    bind_sock(argv[1], sock_fd, ETH_P_ALL);
+
+    while ((num_of_pkts--) > 0) {
+        if (!send_pkt(sock_fd, pkt, PKT_LENGTH)) {
+            perror("Error sending pkt");
+            printf("\n\n");
+            break;
+        }
+    }
+    printf("\nPrinting packet\n");
+    for (i = 0; i < 50; i++)
+        printf("%x ", pkt[i]);
+    if (num_of_pkts == -1)
+        printf("Packets sent successfully\n");
+
+    close(sock_fd);
+    return 0;
+}
diff --git a/tests/test-multipath.c b/tests/test-multipath.c
index b990c13..462a5e1 100644
--- a/tests/test-multipath.c
+++ b/tests/test-multipath.c
@@ -60,7 +60,7 @@ main(int argc, char *argv[])
             struct flow flow;
 
             random_bytes(&flow, sizeof flow);
-            memset(flow.zeros, 0, sizeof flow.zeros);
+            flow_zero_pad(&flow);
 
             mp.max_link = n - 1;
             multipath_execute(&mp, &flow);
diff --git a/utilities/ovs-dpctl.c b/utilities/ovs-dpctl.c
index 6224237..d254d01 100644
--- a/utilities/ovs-dpctl.c
+++ b/utilities/ovs-dpctl.c
@@ -895,14 +895,13 @@ dpctl_normalize_actions(int argc, char *argv[])
 
     hmap_init(&actions_per_flow);
     NL_ATTR_FOR_EACH (a, left, odp_actions.data, odp_actions.size) {
-        if (nl_attr_type(a) == OVS_ACTION_ATTR_POP_VLAN) {
+        const struct ovs_action_push_vlan *push;
+        switch(nl_attr_type(a)) {
+        case OVS_ACTION_ATTR_POP_VLAN:
             flow.vlan_tci = htons(0);
             continue;
-        }
-
-        if (nl_attr_type(a) == OVS_ACTION_ATTR_PUSH_VLAN) {
-            const struct ovs_action_push_vlan *push;
 
+        case OVS_ACTION_ATTR_PUSH_VLAN:
             push = nl_attr_get_unspec(a, sizeof *push);
             flow.vlan_tci = push->vlan_tci;
             continue;
@@ -936,6 +935,15 @@ dpctl_normalize_actions(int argc, char *argv[])
             printf("no vlan: ");
         }
 
+        if (af->flow.mpls_lse != htonl(0)) {
+            printf("mpls(label=%"PRIu32",tc=%d,ttl=%d): ",
+                   mpls_lse_to_label(af->flow.mpls_lse),
+                   mpls_lse_to_tc(af->flow.mpls_lse),
+                   mpls_lse_to_ttl(af->flow.mpls_lse));
+        } else {
+            printf("no mpls: ");
+        }
+
         ds_clear(&s);
         format_odp_actions(&s, af->actions.data, af->actions.size);
         puts(ds_cstr(&s));
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index be71f18..f735fd8 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -451,6 +451,21 @@ integer between 0 and 65535, inclusive, either in decimal or as a
 hexadecimal number prefixed by \fB0x\fR (e.g. \fB0x0806\fR to match ARP 
 packets).
 .
+.IP \fBmpls_label=\fIlabel\fR
+Matches MPLS Label when \fIethertype\fR is \fI0x8847\fR or \fI0x8848\fR.
+Specify a number between 0 and 2^20-1, inclusive, as the 20-bit MPLS label
+to match. If none specified, all packets which has \fIethertype\fR equal to
+\fI0x8847\fR or \fI0x8848\fR are matched.
+.
+.IP \fBmpls_tc=\fIpriority\fR
+Matches MPLS traffic class when \fIethertype\fR is \fI0x8847\fR or
+\fI0x8848\fR.  Specify a value between 0 and 7, inclusive.  A higher value
+indicates a higher packet priority level.
+.
+.IP \fBmpls_stack=\fIstack\fR
+Matches MPLS stack bit when \fIethertype\fR is \fI0x8847\fR or \fI0x8848\fR.
+Specify either 0 or 1 to match a packet with a single label or multiple labels.
+.
 .IP \fBnw_src=\fIip\fR[\fB/\fInetmask\fR]
 .IQ \fBnw_dst=\fIip\fR[\fB/\fInetmask\fR]
 When \fBdl_type\fR is 0x0800 (possibly via shorthand, e.g. \fBip\fR
@@ -897,6 +912,36 @@ as necessary to match the value specified.  Valid values are between 0
 .IP \fBstrip_vlan\fR
 Strips the VLAN tag from a packet if it is present.
 .
+.IP \fBpush_mpls\fR:\fIethertype\fR
+Modifies the ethertype of a packet with the value specified. The new MPLS
+label stack entry is set with value copied from outermost MPLS label stack
+entry if present and stack bit set to 0 or MPLS label stack entry is set with
+\fBlabel\fR:\fI0 for IPv4, 2 for IPv6 or default 0\fR, \fBtc\fR:\fIleast
+significant 3 bits from IPv4 DSCP or IPv6 traffic-class or 0\fR,
+\fBstack\fR:\fI1\fR, \fBttl\fR:\fIIPv4 ttl or IPv6 hlim or default 64\fR.
+.
+.IP \fBpop_mpls\fR:\fIethertype\fR
+Strips the outermost MPLS label stack entry and modifies the ethertype of a
+packet with the value specified if MPLS stack bit is set(i.e. bottom of stack).
+.
+.IP \fBset_mpls_label\fR:\fIlabel\fR
+Modifies the outermost MPLS label stack entry's label field.
+.
+.IP \fBset_mpls_ttl\fR:\fIttl\fR
+Modifies the outermost MPLS label stack entry's time-to-live field.
+.
+.IP \fBset_mpls_tc\fR:\fItc\fR
+Modifies the outermost MPLS label stack entry's traffic-class field.
+.
+.IP \fBcopy_mpls_ttl_in\fR
+Copies ttl from outermost MPLS header to next-to-outermost MPLS or IP header.
+.
+.IP \fBcopy_mpls_ttl_out\fR
+Copies ttl from next-to-outermost IP or MPLS header to outermost MPLS header.
+.
+.IP \fBdec_mpls_ttl\fR
+Decrements ttl from outermost MPLS header.
+.
 .IP \fBmod_dl_src\fB:\fImac\fR
 Sets the source Ethernet address to \fImac\fR.
 .
-- 
1.7.10.4



More information about the dev mailing list