[ovs-dev] [PATCH 5/5] User-Space MPLS actions and matches

Simon Horman horms at verge.net.au
Fri Oct 5 08:05:56 UTC 2012


This patch implements use-space datapath and non-datapath code
to match and use the datapath API set out in Leo Alterman's patch
"user-space datapath: Add basic MPLS support to kernel".

The resulting MPLS implementation supports:
* Pushing a single MPLS label
* Poping a single MPLS label
* Modifying an MPLS lable using set-field or load actions
  that act on the label value, tc and bos bit.
* There is no support for manipulating the TTL
  this is considered future work.

The single-level push pop limitation is implemented by processing
push, pop and set-field/load actions in order and discarding information
that would require multiple levels of push/pop to be supported.

e.g.
   push,push -> the first push is discarded
   pop,pop -> the first pop is discarded

This patch is based heavily on work by Ravi K.

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.1 [Simon Horman]
* Reinstate user-space datapath changes
* Update to use datapath API set out in Leo Alterman's
  kernel datapath patch "datapath: Add basic MPLS support to kernel"
* Do not add:
  - OFPAT11_COPY_TTL_OUT/NXAST_COPY_TTL_OUT,
  - OFPAT11_COPY_TTL_IN/NXAST_COPY_TTL_IN.
  - OFPAT11_DEC_MPLS_TTL/NXAST_DEC_MPLS_TTL
  Add support for these later. Jesse Gross has commented that "It is envisaged
  this will be done "using set and some form of resubmit if necessary (as we do
  for IP)"
* Do not add:
  - OFPAT11_SET_MPLS_LABEL/NXAST_SET_MPLS_LABEL
  - OFPAT11_SET_MPLS_TC/NXAST_SET_MPLS_TC
  - OFPAT11_SET_MPLS_TTL/NXAST_SET_MPLS_TTL
  These may be handled by load/set-field actions.
* Remove inner_mpls_lse from struct flow
  - The new objective is to only support one layer of MPLS labels at this time
* Rearange action handling logic (again):
  - Logic to commit commit all MPLS actions is now in commit_mpls_action()
    which is called by commit_odp_actions().
  - Logic to compose push actions is in compose_mpls_push_action()
  - Logic to compose pop actions is in compose_mpls_pop_action()
  - There is no logic to compose mpls_set actions,
    compose_mpls_lse_action() has been removed.
  - This seems consistent with the handling of other actions.
* Correct mpls formatting in format_odp_action(),
  an error introduced by me.
* s/commit_mpls_/compose_mpls_/
  This seems to be more in keeping with the current naming convention.
* s/mpls_stack/mpls_bos/i
  This seems to be more in keeping with the wording in
  the OpenFlow 1.3 specification

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 uers-space chagges seem
    largely uncontroversial while the datapath
    changes were incomplete with respect to
    agremment 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
---
 include/openflow/nicira-ext.h   |   84 +++++++++
 include/openflow/openflow-1.2.h |    2 +
 lib/dpif-netdev.c               |    2 +-
 lib/flow.c                      |  122 ++++++++++++--
 lib/flow.h                      |   22 ++-
 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                  |  155 ++++++++++++++++-
 lib/ofp-actions.c               |   84 +++++++++
 lib/ofp-actions.h               |   18 ++
 lib/ofp-parse.c                 |   14 ++
 lib/ofp-print.c                 |    4 +
 lib/ofp-util.c                  |   30 +++-
 lib/ofp-util.def                |    4 +
 lib/ofpbuf.c                    |    8 +-
 lib/ofpbuf.h                    |    1 +
 lib/packets.c                   |  356 +++++++++++++++++++++++++++++++++++++++
 lib/packets.h                   |   88 ++++++++++
 ofproto/ofproto-dpif.c          |   96 ++++++++++-
 tests/automake.mk               |    5 +
 tests/odp.at                    |   16 +-
 tests/ofp-print.at              |    6 +-
 tests/ofproto-dpif.at           |  141 +++++++++++++---
 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        |   21 +++
 32 files changed, 1754 insertions(+), 77 deletions(-)
 create mode 100644 tests/test-mpls.c

diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
index 75bf6db..4f11b16 100644
--- a/include/openflow/nicira-ext.h
+++ b/include/openflow/nicira-ext.h
@@ -304,6 +304,8 @@ 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_PUSH_MPLS,            /* struct nx_action_push_mpls */
+    NXAST_POP_MPLS,             /* struct nx_action_pop_mpls */
 };
 
 /* Header for Nicira-defined actions. */
@@ -1757,6 +1759,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_bos 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_BOS     NXM_HEADER  (0x0001, 33, 1)
+
 /* ## --------------------- ## */
 /* ## Requests and replies. ## */
 /* ## --------------------- ## */
@@ -2192,4 +2221,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..53e7f70 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_BOS,       /* MPLS BoS. */
 
     /* 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_BOS       OXM_HEADER   (OFPXMT12_OFB_MPLS_BOS, 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 8228fa2..31c1fb4 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -1208,11 +1208,11 @@ 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_ICMP:
      case OVS_KEY_ATTR_ICMPV6:
      case OVS_KEY_ATTR_ARP:
      case OVS_KEY_ATTR_ND:
-     case OVS_KEY_ATTR_MPLS:
      case __OVS_KEY_ATTR_MAX:
      default:
         NOT_REACHED();
diff --git a/lib/flow.c b/lib/flow.c
index e517a03..d188b26 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -93,6 +93,27 @@ pull_icmpv6(struct ofpbuf *packet)
 }
 
 static void
+parse_remaining_mpls(struct ofpbuf *b)
+{
+    /* Proceed with parsing remaining MPLS headers. */
+    struct mpls_hdr *mh = ofpbuf_pull(b, sizeof *mh);
+    while (!(mh->mpls_lse & htonl(MPLS_BOS_MASK))) {
+        mh = ofpbuf_pull(b, sizeof *mh);
+    }
+}
+
+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 +344,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 +370,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 +392,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_BOS_MASK))) {
+            parse_remaining_mpls(&b);
+        }
+        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 +501,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 +543,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_bos(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 +611,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 +843,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 BOS 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_BOS_MASK);
+    flow->mpls_lse |= htonl(stack << MPLS_BOS_SHIFT);
+}
+
 /* Puts into 'b' a packet that flow_extract() would parse as having the given
  * 'flow'.
  *
@@ -797,7 +885,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 +901,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 +947,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 +969,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..2d07d47 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,11 @@ 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. */
     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 +81,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 +98,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 +122,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..d88e3a9 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_BOS_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_BOS_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_BOS_MASK)) {
+        ds_put_format(s, "mpls_stack=%"PRIu8",",
+                 mpls_lse_to_bos(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 0a5d1ac..52dde96 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",
+        OXM_OF_MPLS_LABEL, "OXM_OF_MPLS_LABEL",
+    }, {
+        MFF_MPLS_TC, "mpls_tc", NULL,
+        1, 3,
+        MFM_NONE,
+        MFS_DECIMAL,
+        MFP_MPLS,
+        true,
+        NXM_NX_MPLS_TC, "NXM_NX_MPLS_TC",
+        OXM_OF_MPLS_TC, "OXM_OF_MPLS_TC",
+    }, {
+        MFF_MPLS_BOS, "mpls_bos", NULL,
+        1, 1,
+        MFM_NONE,
+        MFS_DECIMAL,
+        MFP_MPLS,
+        true,
+        NXM_NX_MPLS_BOS, "NXM_NX_MPLS_BOS",
+        OXM_OF_MPLS_BOS, "OXM_OF_MPLS_BOS",
+    },
+
     /* ## -- ## */
     /* ## 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_BOS:
+        return !(wc->masks.mpls_lse & htonl(MPLS_BOS_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_BOS:
+        mask->u8 = mpls_lse_to_bos(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_BOS:
+        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_BOS:
+        value->u8 = mpls_lse_to_bos(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_BOS:
+        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_BOS:
+        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_BOS:
+        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_BOS:
     case MFF_IP_PROTO:
     case MFF_IP_TTL:
     case MFF_IP_DSCP:
@@ -1794,6 +1906,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_BOS:
+        value->u8 &= 0x01;
+        break;
+
     case MFF_N_IDS:
     default:
         NOT_REACHED();
diff --git a/lib/meta-flow.h b/lib/meta-flow.h
index 3fe9014..46e6063 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_BOS,               /* 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 0b513c1..dfc941c 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_BOS_MASK)) {
+            nxm_put_8(b, oxm ? OXM_OF_MPLS_BOS : NXM_NX_MPLS_BOS,
+                      mpls_lse_to_bos(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 db3b037..c47269e 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>
@@ -283,9 +284,17 @@ format_mpls_lse(struct ds *ds, ovs_be32 mpls_lse)
                   mpls_lse_to_label(mpls_lse),
                   mpls_lse_to_tc(mpls_lse),
                   mpls_lse_to_ttl(mpls_lse),
-                  mpls_lse_to_stack(mpls_lse));
+                  mpls_lse_to_bos(mpls_lse));
 }
 
+static ovs_be32
+format_mpls_lse_values(int mpls_label, int mpls_tc, int mpls_ttl, int mpls_bos)
+{
+    return(htonl((mpls_label << MPLS_LABEL_SHIFT) |
+                 (mpls_tc << MPLS_TC_SHIFT)       |
+                 (mpls_ttl << MPLS_TTL_SHIFT)     |
+                 (mpls_bos << MPLS_BOS_SHIFT)));
+}
 
 static void
 format_odp_action(struct ds *ds, const struct nlattr *a)
@@ -330,7 +339,7 @@ format_odp_action(struct ds *ds, const struct nlattr *a)
         const struct ovs_action_push_mpls *mpls = nl_attr_get(a);
         ds_put_cstr(ds, "push_mpls(");
         format_mpls_lse(ds, mpls->mpls_label);
-        ds_put_format(ds, "eth_type=0x%"PRIx16")", ntohs(mpls->mpls_ethertype));
+        ds_put_format(ds, ",eth_type=0x%"PRIx16")", ntohs(mpls->mpls_ethertype));
         break;
     }
     case OVS_ACTION_ATTR_POP_MPLS: {
@@ -1027,6 +1036,22 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
     }
 
     {
+        int label, tc, ttl, bos;
+        int n = -1;
+
+        if (sscanf(s, "mpls(label=%"SCNi32",tc=%i,ttl=%i,bos=%i)%n",
+                    &label, &tc, &ttl, &bos, &n) > 0 &&
+                    n > 0) {
+            struct ovs_key_mpls *mpls;
+
+            mpls = nl_msg_put_unspec_uninit(key, OVS_KEY_ATTR_MPLS,
+                                            sizeof *mpls);
+            mpls->mpls_top_label = format_mpls_lse_values(label, tc, ttl, bos);
+            return n;
+        }
+    }
+
+    {
         ovs_be32 ipv4_src;
         ovs_be32 ipv4_dst;
         int ipv4_proto;
@@ -1370,6 +1395,14 @@ odp_flow_key_from_flow(struct ofpbuf *buf, const struct flow *flow)
         memcpy(arp_key->arp_tha, flow->arp_tha, ETH_ADDR_LEN);
     }
 
+    if (flow->mpls_lse != htonl(0)) {
+        struct ovs_key_mpls *mpls_key;
+
+        mpls_key = nl_msg_put_unspec_uninit(buf, OVS_KEY_ATTR_MPLS,
+                                            sizeof *mpls_key);
+        mpls_key->mpls_top_label = flow->mpls_lse;
+    }
+
     if ((flow->dl_type == htons(ETH_TYPE_IP)
          || flow->dl_type == htons(ETH_TYPE_IPV6))
         && !(flow->nw_frag & FLOW_NW_FRAG_LATER)) {
@@ -1553,6 +1586,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,
@@ -1576,13 +1623,79 @@ 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;
+
+    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;
@@ -1752,8 +1865,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);
@@ -1832,6 +1947,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);
 }
@@ -1945,6 +2061,33 @@ commit_vlan_action(const struct flow *flow, struct flow *base,
 }
 
 static void
+commit_mpls_action(const struct flow *flow, struct flow *base,
+                   struct ofpbuf *odp_actions)
+{
+    if (flow->mpls_lse == base->mpls_lse) {
+        return;
+    }
+
+    if (base->mpls_lse != htonl(0) && flow->mpls_lse == htonl(0)) {
+        nl_msg_put_be16(odp_actions, OVS_ACTION_ATTR_POP_MPLS,
+                        flow->dl_type);
+    } else if (base->mpls_lse == htonl(0) && flow->mpls_lse != htonl(0)) {
+        struct ovs_action_push_mpls *mpls;
+
+        mpls = nl_msg_put_unspec_uninit(odp_actions, OVS_ACTION_ATTR_PUSH_MPLS,
+                                        sizeof *mpls);
+        memset(mpls, 0, sizeof *mpls);
+        mpls->mpls_ethertype = flow->dl_type;
+        mpls->mpls_label = flow->mpls_lse;
+    } else {
+        nl_msg_put_be32(odp_actions, OVS_ACTION_ATTR_SET_MPLS, flow->mpls_lse);
+    }
+
+    base->dl_type = flow->dl_type;
+    base->mpls_lse = flow->mpls_lse;
+}
+
+static void
 commit_set_ipv4_action(const struct flow *flow, struct flow *base,
                      struct ofpbuf *odp_actions)
 {
@@ -1998,7 +2141,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)
@@ -2071,6 +2213,7 @@ commit_odp_actions(const struct flow *flow, struct flow *base,
     commit_set_tun_id_action(flow, base, odp_actions);
     commit_set_ether_addr_action(flow, base, odp_actions);
     commit_vlan_action(flow, base, odp_actions);
+    commit_mpls_action(flow, base, odp_actions);
     commit_set_nw_action(flow, base, odp_actions);
     commit_set_port_action(flow, base, odp_actions);
     commit_set_priority_action(flow, base, odp_actions);
diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
index 24ee741..67c9648 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -378,6 +378,18 @@ 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_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 +734,18 @@ 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_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;
+    }
+
 #define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) case OFPUTIL_##ENUM:
 #include "ofp-util.def"
         return ofpact_from_nxast(a, code, out);
@@ -1040,6 +1064,24 @@ ofpact_check__(const struct ofpact *a, 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;
+    }
+
     default:
         NOT_REACHED();
     }
@@ -1242,6 +1284,16 @@ ofpact_to_nxast(const struct ofpact *a, struct ofpbuf *out)
         ofputil_put_NXAST_EXIT(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_OUTPUT:
     case OFPACT_ENQUEUE:
     case OFPACT_SET_VLAN_VID:
@@ -1359,6 +1411,8 @@ ofpact_to_openflow10(const struct ofpact *a, struct ofpbuf *out)
     case OFPACT_AUTOPATH:
     case OFPACT_NOTE:
     case OFPACT_EXIT:
+    case OFPACT_PUSH_MPLS:
+    case OFPACT_POP_MPLS:
         ofpact_to_nxast(a, out);
         break;
     }
@@ -1451,6 +1505,16 @@ ofpact_to_openflow11(const struct ofpact *a, struct ofpbuf *out)
             = htons(ofpact_get_SET_L4_DST_PORT(a)->port);
         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_CONTROLLER:
     case OFPACT_OUTPUT_REG:
     case OFPACT_BUNDLE:
@@ -1548,6 +1612,9 @@ ofpact_outputs_to_port(const struct ofpact *ofpact, uint16_t port)
     case OFPACT_AUTOPATH:
     case OFPACT_NOTE:
     case OFPACT_EXIT:
+    case OFPACT_PUSH_MPLS:
+    case OFPACT_POP_MPLS:
+
     default:
         return false;
     }
@@ -1638,6 +1705,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:
@@ -1811,6 +1885,16 @@ ofpact_format(const struct ofpact *a, struct ds *s)
         print_note(ofpact_get_NOTE(a), s);
         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 b68de67..4ef66e5 100644
--- a/lib/ofp-actions.h
+++ b/lib/ofp-actions.h
@@ -70,6 +70,8 @@
     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(PUSH_MPLS,       ofpact_push,          ofpact)    \
+    DEFINE_OFPACT(POP_MPLS,        ofpact_pop_mpls,      ofpact)    \
                                                                     \
     /* Metadata. */                                                 \
     DEFINE_OFPACT(SET_TUNNEL,      ofpact_tunnel,        ofpact)    \
@@ -302,6 +304,22 @@ struct ofpact_reg_load {
     union mf_subvalue subvalue; /* Least-significant bits are used. */
 };
 
+/* 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..78048e8 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -517,6 +517,18 @@ parse_named_action(enum ofputil_action_code code, const struct flow *flow,
     case OFPUTIL_NXAST_CONTROLLER:
         parse_controller(ofpacts, arg);
         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 +586,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 9123f2f..11a88ad 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 7edae87..1fa21fe 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_BOS_MASK)) {
+        return OFPUTIL_P_NXM_ANY;
+    }
+
     /* Other formats can express this rule. */
     return OFPUTIL_P_ANY;
 }
@@ -3755,7 +3770,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;
@@ -3783,6 +3799,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;
     }
@@ -3815,6 +3834,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..2fc22d1 100644
--- a/lib/ofp-util.def
+++ b/lib/ofp-util.def
@@ -32,6 +32,8 @@ 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_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 +62,8 @@ 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_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..0b85cf9 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_BOS_MASK);
+    *tag |= stack & htonl(MPLS_BOS_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_BOS_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_BOS_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_BOS_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_BOS_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..299da8f 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_BOS_MASK       0x00000100
+#define MPLS_BOS_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_bos(ovs_be32 mpls_lse)
+{
+    return (mpls_lse & htonl(MPLS_BOS_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 dcdd8f2..90fecc7 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -3933,6 +3933,7 @@ subfacet_should_install(struct subfacet *subfacet, enum slow_path_reason slow,
                         const struct ofpbuf *want_actions)
 {
     enum subfacet_path want_path = subfacet_want_path(slow);
+
     return (want_path != subfacet->path
             || (want_path == SF_FAST_PATH
                 && (subfacet->actions_len != want_actions->size
@@ -4951,6 +4952,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;
 
@@ -4988,6 +4990,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;
 }
 
@@ -5135,9 +5138,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);
@@ -5146,6 +5160,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,
@@ -5178,6 +5203,55 @@ execute_controller_action(struct action_xlate_ctx *ctx, int len,
     ofpbuf_delete(packet);
 }
 
+static void
+compose_mpls_push_action(struct action_xlate_ctx *ctx, ovs_be16 eth_type)
+{
+    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_BOS_MASK);
+    } else {
+        ovs_be32 mpls_label, mpls_tc, mpls_ttl, mpls_stack;
+
+        ctx->flow.mpls_lse &= ~htonl(MPLS_LABEL_MASK | MPLS_TC_MASK |
+                                     MPLS_TTL_MASK | MPLS_BOS_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_BOS_SHIFT);
+        ctx->flow.mpls_lse = mpls_label | mpls_tc | mpls_ttl | mpls_stack;
+        ctx->flow.encap_dl_type = ctx->flow.dl_type;
+    }
+    ctx->flow.dl_type = eth_type;
+}
+
+static void
+compose_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));
+
+    nl_msg_put_be16(ctx->odp_actions, OVS_ACTION_ATTR_POP_MPLS, eth_type);
+ 
+    if (ctx->flow.mpls_lse & htonl(MPLS_BOS_MASK)) {
+        ctx->flow.dl_type = eth_type;
+        ctx->flow.encap_dl_type = htons(0);
+    }
+    ctx->base_flow.mpls_lse = htonl(0);
+}
+
 static bool
 compose_dec_ttl(struct action_xlate_ctx *ctx, struct ofpact_cnt_ids *ids)
 {
@@ -5552,8 +5626,20 @@ 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);
+            break;
+        }
+
+        case OFPACT_PUSH_MPLS:
+            compose_mpls_push_action(ctx, ofpact_get_PUSH_MPLS(a)->ethertype);
+            break;
+
+        case OFPACT_POP_MPLS:
+            compose_mpls_pop_action(ctx, ofpact_get_POP_MPLS(a)->ethertype);
+            ctx->flow.mpls_lse = htonl(0);
             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 fee3d28..bf2ef60 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..093c287 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,10 @@ 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,load:10->NXM_NX_MPLS_LABEL[[]],load:3->NXM_NX_MPLS_TC[[]],controller
+cookie=0xb dl_src=50:55:55:55:55:55 actions=load:1000->NXM_NX_MPLS_LABEL[[]],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,load:1000->NXM_NX_MPLS_LABEL[[]],load:7->NXM_NX_MPLS_TC[[]],controller
 ])
 AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
 
@@ -263,13 +267,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 +286,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 +305,90 @@ 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:64,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:64,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:64,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 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:66,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:66,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:66,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 +401,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 +438,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 +475,10 @@ 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,load:0xa->NXM_NX_MPLS_LABEL[[]],load:0x3->NXM_NX_MPLS_TC[[]],CONTROLLER:65535
+ cookie=0xb, n_packets=3, n_bytes=180, dl_src=50:55:55:55:55:55 actions=load:0x3e8->NXM_NX_MPLS_LABEL[[]],CONTROLLER:65535
+ cookie=0xc, n_packets=3, n_bytes=180, dl_src=70:77:77:77:77:77 actions=push_mpls:0x8848,load:0x3e8->NXM_NX_MPLS_LABEL[[]],load:0x7->NXM_NX_MPLS_TC[[]],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 455077b..c30a671 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -631,21 +631,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
@@ -743,9 +743,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:
 ])
 
@@ -773,7 +773,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..b38f53c
--- /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_BOS_SHIFT) & MPLS_BOS_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 4f62c66..4a9fe1b 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -897,6 +897,27 @@ 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
+There are some limitations in the implementation.
+push_mpls followed by pop_mpls will be synthesised to a no-op. push_mpls
+followed by another push_mpls will result in the first push_mpls being
+discarded.
+.
+.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
+There are some limitations in the implementation.
+push_mpls followed by pop_mpls will be synthesised to a no-op. pop_mpls
+followed by another push_mpls without an intermediate push_mpls will result in the first push_mpls being discarded.
+.
 .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