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

Simon Horman horms at verge.net.au
Thu Jan 17 06:25:00 UTC 2013


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: Ravi K <rkerur at gmail.com>
Reviewed-by: Isaku Yamahata <yamahata at valinux.co.jp>
Signed-off-by: Simon Horman <horms at verge.net.au>

---

v2.15
* Rebase for ovs_assert
* Under discussion with Jesse Gross
  - Move OVS_KEY_ATTR_MPLS to after all upstream attributes
  - Move OVS_ACTION_ATTR_PUSH_MPLS and OVS_ACTION_ATTR_POP_MPLS
    to after all upstream attributes
* As suggested by Ben Pfaff
  - Use OVS_ACTION_SET to set OVS_KEY_ATTR_MPLS instead of
    OVS_ACTION_ATTR_SET_MPLS
  - Rename nx_action_push as nx_action_push_mpls and correct comments
    + There is no NXAST_PUSH_VLAN action
  - Merge parse_remaining_mpls() into parse_mpls()
  - Make order of MFF_MPLS_LABEL, MFF_MPLS_TC and MFF_MPLS_BOS in
    mf_is_value_valid() consistent with other similar functions.
  - Use value->u8 instead of value->be32 in mf_is_value_valid()
    when checking MFF_MPLS_TC and MFF_MPLS_BOS.
  - Log if MPLS_PUS/POP causes an MPLS stack depth change of more than one,
    indicating that several actions have been compressed into one in a
    lossy fashion
  - Rename struct ofpact_push as struct ofpact_push_mpls and remove mention
    of non-MPLS actions from its comment
  - Remove note about OF1.3 session negotiation not being supported yet
    from ofputil_usable_protocols() - it is supported now.
  - Mask off wc.masks.mpls_depth in ofputil_normalize_match__()
  - Do not add double blank line to nx_put_raw()
  - Add missing whitespace after , in nx_put_raw()
  - Remove double ';' in parse_l3_onward
  - Add L2.5 category to mf_prereqs
* parse_mpls(): Don't check for at least 16 trailing bytes after
  the first MPLS header. It seems unnecessary and is inconsistent
  with handling of subsequent headers.
* parse_mpls(): Count all MPLS headers in mpls_depth, previously
  if there was more than one MPLS header then mpls_depth did not
  include the last header.

v2.14
* Add include/linux/openvswitch.h portion to add
  new key and action attributes. This was
  previously present in "datapath: Add basic MPLS support to kernel"
  which was previously a dependency of this patch.

v2.13
* As suggested by Jarno Rajahalme
  - Update ofputil_usable_protocols to allow MPLS matches for OpenFlow 1.3.
  - Update ofpact_check__ to only copy the flow when checking
    OFPACT_REG_LOAD if (*dl_type != flow->dl_type)
* Fix inverted logic in flow_innermost_dl_type

v2.12
* Rebase
* Add eth_type_mpls helper
* Add flow_innermost_dl_type helper
* calculate MPLS fitness over entire flow
  - It is sufficient to just calculate the fitness of an MPLS frame once.
  - Previously a fitness calculation was made based on expectations calculated
    on L1 and L2 data. However, this may not be correct as it will return
    ODP_FIT_TOO_MUCH in the case where the key contains L3 attributes.
* Pass MPLS lse to push_mpls
  - When push_mpls() the LSE of the flow is always already known,
    so pass it to push_mpls and set it directly. This avoids:
    + The need to re-compose an default LSE, allowing
      the removal of get_lse()
    + The need to follow each call to push_mpls() with
      a call to set_mpls_lse()
  - This fixes the previously bogus usage of push_mpls
    in dp_netdev_execute_actions, allowing push_mpls to work
    in conjunction with the user-space datapath.

v2.11
* No change

v2.10
* Rebase

v2.9
* Rebase
* Update tests for upstream changes

v2.8
* Rebase, update for upstream OFPUTIL_P_* changes

v2.7
* No change

v2.6
* Non-trivial rebase
* Track dl_type changes in ofpact_check__() and ofpacts_check()
  to allow pre-requisite checks to work correctly in the presence
  of mpls_push and mpls_pop actions.
* Do not modify datapath.
  Accordingly, the changes suggested by Yamahata-san which were
  incorporated in v2.5 to the datapath patch "datapath: Add basic MPLS
  support to kernel"

v2.5
* As suggested by Yamahata-san
  - Do not guard against label == 0 for
    OVS_ACTION_ATTR_SET_MPLS in validate_actions().
    A label of 0 is valid
  - Remove comment stipulating that if
    the top_label element of struct sw_flow_key is 0 then
    there is no MPLS label. An MPLS label of 0 is valid
    and the correct check if ethertype is
    ntohs(ETH_TYPE_MPLS) or ntohs(ETH_TYPE_MPLS_MCAST)
* Update tests for TTL fix provided by
  "flow: Set ip_ttl in flow_compose()"
* Do not initialise ih and ih6 in flow_extract()
  until after calling parse_mpls() and parse_remaining_mpls()
  as those functions may change the value of b.data.
* Do not add spurious debug code to execute_controller_action()
* Do not reset packet->l3 in pop_mpls(),
  it is already the correct value

v2.4
* Make use of set_mpls_lse_*() in flow_set_mpls_*()

v2.3
* As suggested by Ben Pfaff
  - Remove noise-only change from execute_set_action()
  - Guard against running off the end of packets in parse_remaining_mpls()
  - Ensure there is sufficient data before reading IP header in flow_extract()
  - Omit formating mpls in flow_format if there is no MPLS header
  - Fix spelling errors in parse_mpls_onward()
  - Fix coding style in flow_compose()
  - Use inner_dl_type where possible in flow_compose()
  - Correct bitwise logic in mpls_set_any_*()
  - Do not define NXM constants for MPLS as the OXM constants may be used
    with NXM.
  - Disable writing of MPLS BOS by setting 'writable' to false for
    OXM_OF_MPLS_BOS.
  - Rename format_mpls_lse_values() as mpls_lse_from_components()
    as it is not a format helper.
  - Fix coding style in mpls_lse_from_components()
  - Evaluate check_expectations() outside of MAX
    as MAX evaluates its arguments more than once.
  - Remove noise-change from parse_8021q_onward()
  - Remove noise-change from commit_set_nw_action()
  - Move action checking logic from ofpact_check__() to
    ofpact_from_openflow11() and ofpact_from_nxast()
  - Remove noise-change from ofpact_format
  - Fix typos in ofpact_push
  - Correct comment in ofputil_usable_protocols()
    MPLS BoS bit match was added in OpenFlow 1.3
  - Add a helper function to return the pointer to the eth_type of a packet
    and use this to vastly simplify set_ethertype() and get_ethertype()
  - Correct size logic in get_label_ttl_and_tc() using
    ofpbuf_tail(packet) - packet->l3
  - Refactor get_label_ttl_and_tc to return an MPLS tag
    rather than broken out TTL, TC and value. Also,
    rename as get_lse()
  - Remove set_new_mpls_lse and open code its logic.
  - Correct code style of if block in pop_mpls()
  - Do not add unused IP6_TC()
  - Do not make spurious change to subfacet_should_install()
  - Remove assert() from execute_controller_action() as it
    is not obviously useful.
  - Make use of set_mpls_lse_values() in compose_mpls_push_action()
  - Do not make spurious change for REG_LOAD in do_xlate_actions()
  - Do not at test-mpls, it is not used
* As suggested by Yamahata-san
  - Use MPLS_xxx_MASK >> MPLS_xxx_SHIFT in mf_is_value_valid() and
  - Track MPLS stack depth and use it to determine if the MPLS label should
    be pushed, popped or modified
  - No longer use 0 as a special MPLS LSE value
* s/mpls_stack/mpls_bos/
  This is in keeping with the naming used in the OpenFlow 1.3 specification
* Fold user-space datapath back into this patch,
  there seem to be mutual dependencies.
* Remove stray structure definitions: nx_action_mpls_label,
  nx_action_mpls_tc, nx_action_mpls_ttl
* Remove check_expectations_mpls() and use check_expectations() directly
  instead.  The logic of check_expectations_mpls() is not relevant to the
  current incantation of the patch-set as it includes support for MPLS
  matches in the datapath.
* Removed unused TTL manipulation code

v2.2
* Manual Rebase

v2.1
* 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
* Rearrange 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
* 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
    agreement on the implementation for TTL actions
    and offloads.
* Correct dec_mpls_ttl to decrement 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/linux/openvswitch.h   |   34 +++++++-
 include/openflow/nicira-ext.h |   24 ++++++
 lib/dpif-netdev.c             |   14 +++
 lib/flow.c                    |   74 +++++++++++++---
 lib/flow.h                    |   25 +++++-
 lib/match.c                   |   85 +++++++++++++++++-
 lib/match.h                   |    6 ++
 lib/meta-flow.c               |  113 ++++++++++++++++++++++++
 lib/meta-flow.h               |   11 +++
 lib/nx-match.c                |   18 +++-
 lib/odp-util.c                |  154 ++++++++++++++++++++++++++++++---
 lib/ofp-actions.c             |   94 +++++++++++++++++++-
 lib/ofp-actions.h             |   18 ++++
 lib/ofp-parse.c               |   16 +++-
 lib/ofp-print.c               |    4 +
 lib/ofp-util.c                |   36 ++++++--
 lib/ofp-util.def              |    4 +
 lib/ofpbuf.c                  |    8 +-
 lib/ofpbuf.h                  |    1 +
 lib/packets.c                 |  189 ++++++++++++++++++++++++++++++++++++++++-
 lib/packets.h                 |   96 +++++++++++++++++++++
 ofproto/ofproto-dpif.c        |   76 +++++++++++++++--
 tests/odp.at                  |   13 +++
 tests/ofproto-dpif.at         |   85 ++++++++++++++++++
 tests/test-bundle.c           |    1 +
 tests/test-multipath.c        |    1 +
 utilities/ovs-dpctl.c         |   18 ++--
 utilities/ovs-ofctl.8.in      |   20 +++++
 28 files changed, 1187 insertions(+), 51 deletions(-)

diff --git a/include/linux/openvswitch.h b/include/linux/openvswitch.h
index 5e32965..d458491 100644
--- a/include/linux/openvswitch.h
+++ b/include/linux/openvswitch.h
@@ -283,6 +283,7 @@ enum ovs_key_attr {
 	OVS_KEY_ATTR_ND,        /* struct ovs_key_nd */
 	OVS_KEY_ATTR_SKB_MARK,  /* u32 skb mark */
 	OVS_KEY_ATTR_IPV4_TUNNEL,  /* struct ovs_key_ipv4_tunnel */
+	OVS_KEY_ATTR_MPLS,      /* struct ovs_key_mpls */
 	OVS_KEY_ATTR_TUN_ID = 63,  /* be64 tunnel ID */
 	__OVS_KEY_ATTR_MAX
 };
@@ -312,6 +313,10 @@ struct ovs_key_ethernet {
 	__u8	 eth_dst[6];
 };
 
+struct ovs_key_mpls {
+	__be32 mpls_top_label;
+};
+
 struct ovs_key_ipv4 {
 	__be32 ipv4_src;
 	__be32 ipv4_dst;
@@ -457,6 +462,19 @@ enum ovs_userspace_attr {
 #define OVS_USERSPACE_ATTR_MAX (__OVS_USERSPACE_ATTR_MAX - 1)
 
 /**
+ * struct ovs_action_push_mpls - %OVS_ACTION_ATTR_PUSH_MPLS action argument.
+ * @mpls_label: MPLS label to push.
+ * @mpls_ethertype: Ethertype to set in the encapsulating ethernet frame.
+ *
+ * The only values @mpls_ethertype should ever be given are %ETH_P_MPLS_UC and
+ * %ETH_P_MPLS_MC, indicating MPLS unicast or multicast. Other are rejected.
+ */
+struct ovs_action_push_mpls {
+	__be32 mpls_label;
+	__be16 mpls_ethertype; /* Either %ETH_P_MPLS_UC or %ETH_P_MPLS_MC */ 
+};
+
+/**
  * struct ovs_action_push_vlan - %OVS_ACTION_ATTR_PUSH_VLAN action argument.
  * @vlan_tpid: Tag protocol identifier (TPID) to push.
  * @vlan_tci: Tag control identifier (TCI) to push.  The CFI bit must be set
@@ -478,14 +496,22 @@ struct ovs_action_push_vlan {
  * @OVS_ACTION_ATTR_OUTPUT: Output packet to port.
  * @OVS_ACTION_ATTR_USERSPACE: Send packet to userspace according to nested
  * %OVS_USERSPACE_ATTR_* attributes.
- * @OVS_ACTION_ATTR_SET: Replaces the contents of an existing header.  The
- * single nested %OVS_KEY_ATTR_* attribute specifies a header to modify and its
- * value.
  * @OVS_ACTION_ATTR_PUSH_VLAN: Push a new outermost 802.1Q header onto the
  * packet.
  * @OVS_ACTION_ATTR_POP_VLAN: Pop the outermost 802.1Q header off the packet.
  * @OVS_ACTION_ATTR_SAMPLE: Probabilitically executes actions, as specified in
  * the nested %OVS_SAMPLE_ATTR_* attributes.
+ * @OVS_ACTION_ATTR_SET: Replaces the contents of an existing header.  The
+ * single nested %OVS_KEY_ATTR_* attribute specifies a header to modify and its
+ * value.
+ * @OVS_ACTION_ATTR_PUSH_MPLS: Push a new MPLS label onto the top of the packet
+ * MPLS stack. Set the ethertype of the encapsulating frame to either
+ * %ETH_P_MPLS_UC or %ETH_P_MPLS_MC to indicate the new packet contents.
+ * @OVS_ACTION_ATTR_POP_MPLS: Pop an MPLS label off of the packet's MPLS stack.
+ * Set the encapsulating frame's ethertype to indicate the new packet contents
+ * (this could potentially still be %ETH_P_MPLS_* if there are remaining MPLS
+ * labels).  If there are no MPLS labels as determined by ethertype, no action
+ * is taken.
  *
  * Only a single header can be set with a single %OVS_ACTION_ATTR_SET.  Not all
  * fields within a header are modifiable, e.g. the IPv4 protocol and fragment
@@ -500,6 +526,8 @@ enum ovs_action_attr {
 	OVS_ACTION_ATTR_PUSH_VLAN,    /* struct ovs_action_push_vlan. */
 	OVS_ACTION_ATTR_POP_VLAN,     /* No argument. */
 	OVS_ACTION_ATTR_SAMPLE,       /* Nested OVS_SAMPLE_ATTR_*. */
+	OVS_ACTION_ATTR_PUSH_MPLS,    /* struct ovs_action_push_mpls. */
+	OVS_ACTION_ATTR_POP_MPLS,     /* __be16 ethertype. */
 	__OVS_ACTION_ATTR_MAX
 };
 
diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
index 91c96b3..d6a99ce 100644
--- a/include/openflow/nicira-ext.h
+++ b/include/openflow/nicira-ext.h
@@ -304,6 +304,8 @@ enum nx_action_subtype {
     NXAST_CONTROLLER,           /* struct nx_action_controller */
     NXAST_DEC_TTL_CNT_IDS,      /* struct nx_action_cnt_ids */
     NXAST_WRITE_METADATA,       /* struct nx_action_write_metadata */
+    NXAST_PUSH_MPLS,            /* struct nx_action_push_mpls */
+    NXAST_POP_MPLS,             /* struct nx_action_pop_mpls */
 };
 
 /* Header for Nicira-defined actions. */
@@ -2212,4 +2214,26 @@ struct nx_action_write_metadata {
 };
 OFP_ASSERT(sizeof(struct nx_action_write_metadata) == 32);
 
+/* Action structure for NXAST_PUSH_MPLS. */
+struct nx_action_push_mpls {
+    ovs_be16 type;                  /* OFPAT_PUSH_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_mpls) == 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/lib/dpif-netdev.c b/lib/dpif-netdev.c
index bec32c3..0c41a5a 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -1224,6 +1224,10 @@ execute_set_action(struct ofpbuf *packet, const struct nlattr *a)
         packet_set_udp_port(packet, udp_key->udp_src, udp_key->udp_dst);
         break;
 
+     case OVS_KEY_ATTR_MPLS:
+             set_mpls_lse(packet, nl_attr_get_be32(a));
+             break;
+
      case OVS_KEY_ATTR_UNSPEC:
      case OVS_KEY_ATTR_ENCAP:
      case OVS_KEY_ATTR_ETHERTYPE:
@@ -1270,6 +1274,16 @@ dp_netdev_execute_actions(struct dp_netdev *dp,
             eth_pop_vlan(packet);
             break;
 
+        case OVS_ACTION_ATTR_PUSH_MPLS: {
+            const struct ovs_action_push_mpls *mpls = nl_attr_get(a);
+            push_mpls(packet, mpls->mpls_ethertype, mpls->mpls_label);
+            break;
+         }
+
+        case OVS_ACTION_ATTR_POP_MPLS:
+             pop_mpls(packet, nl_attr_get_be16(a));
+             break;
+
         case OVS_ACTION_ATTR_SET:
             execute_set_action(packet, nl_attr_get(a));
             break;
diff --git a/lib/flow.c b/lib/flow.c
index 861a1f1..7550b44 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -93,6 +93,21 @@ pull_icmpv6(struct ofpbuf *packet)
 }
 
 static void
+parse_mpls(struct ofpbuf *b, struct flow *flow)
+{
+    struct mpls_hdr *mh;
+
+    while ((mh = ofpbuf_try_pull(b, sizeof *mh))) {
+        if (flow->mpls_depth++ == 0) {
+            flow->mpls_lse = mh->mpls_lse;
+        }
+        if (mh->mpls_lse & htonl(MPLS_BOS_MASK)) {
+            break;
+        }
+    }
+}
+
+static void
 parse_vlan(struct ofpbuf *b, struct flow *flow)
 {
     struct qtag_prefix {
@@ -323,6 +338,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.
@@ -353,10 +370,11 @@ flow_extract(struct ofpbuf *packet, uint32_t skb_priority, uint32_t skb_mark,
     flow->skb_priority = skb_priority;
     flow->skb_mark = skb_mark;
 
-    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;
@@ -374,6 +392,12 @@ flow_extract(struct ofpbuf *packet, uint32_t skb_priority, uint32_t skb_mark,
     }
     flow->dl_type = parse_ethertype(&b);
 
+    /* Parse mpls, copy l3 ttl. */
+    if (eth_type_mpls(flow->dl_type)) {
+        packet->l2_5 = b.data;
+        parse_mpls(&b, flow);
+    }
+
     packet->l3 = b.data;
     flow_extract_l3_onwards(packet, flow, flow->dl_type);
 }
@@ -486,7 +510,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 == 18);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 19);
 
     fmd->tun_id = flow->tunnel.tun_id;
     fmd->metadata = flow->metadata;
@@ -810,6 +834,29 @@ 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)
+{
+    set_mpls_lse_label(&flow->mpls_lse, label);
+}
+
+/* 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)
+{
+    set_mpls_lse_tc(&flow->mpls_lse, tc);
+}
+
+/* Sets the MPLS BOS bit that 'flow' matches to which should be 0 or 1. */
+void
+flow_set_mpls_bos(struct flow *flow, uint8_t bos)
+{
+    set_mpls_lse_bos(&flow->mpls_lse, bos);
+}
+
 /* Puts into 'b' a packet that flow_extract() would parse as having the given
  * 'flow'.
  *
@@ -819,7 +866,10 @@ 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_innermost_dl_type(flow);
+    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);
@@ -830,7 +880,7 @@ 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 (inner_dl_type == htons(ETH_TYPE_IP)) {
         struct ip_header *ip;
 
         b->l3 = ip = ofpbuf_put_zeros(b, sizeof *ip);
@@ -876,10 +926,10 @@ 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 (inner_dl_type == htons(ETH_TYPE_IPV6)) {
         /* XXX */
-    } else if (flow->dl_type == htons(ETH_TYPE_ARP) ||
-               flow->dl_type == htons(ETH_TYPE_RARP)) {
+    } else if (inner_dl_type == htons(ETH_TYPE_ARP) ||
+               inner_dl_type == htons(ETH_TYPE_RARP)) {
         struct arp_eth_header *arp;
 
         b->l3 = arp = ofpbuf_put_zeros(b, sizeof *arp);
@@ -897,6 +947,10 @@ flow_compose(struct ofpbuf *b, const struct flow *flow)
             memcpy(arp->ar_tha, flow->arp_tha, ETH_ADDR_LEN);
         }
     }
+
+    if (eth_type_mpls(flow->dl_type)) {
+        push_mpls(b, flow->dl_type, flow->mpls_lse);
+    }
 }
 
 /* Compressed flow. */
diff --git a/lib/flow.h b/lib/flow.h
index 8e79e62..3078332 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 18
+#define FLOW_WC_SEQ 19
 
 #define FLOW_N_REGS 8
 BUILD_ASSERT_DECL(FLOW_N_REGS <= NXM_NX_MAX_REGS);
@@ -91,8 +91,11 @@ struct flow {
                                    unless in DPIF code, in which case it
                                    is the datapath port number. */
     uint32_t skb_mark;          /* Packet mark. */
+    ovs_be32 mpls_lse;          /* MPLS label stack entry. */
+    uint16_t mpls_depth;        /* Depth of MPLS stack. */
     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. */
@@ -110,8 +113,8 @@ BUILD_ASSERT_DECL(sizeof(struct flow) % 4 == 0);
 #define FLOW_U32S (sizeof(struct flow) / 4)
 
 /* Remember to update FLOW_WC_SEQ when changing 'struct flow'. */
-BUILD_ASSERT_DECL(sizeof(struct flow) == sizeof(struct flow_tnl) + 152 &&
-                  FLOW_WC_SEQ == 18);
+BUILD_ASSERT_DECL(sizeof(struct flow) == sizeof(struct flow_tnl) + 160 &&
+                  FLOW_WC_SEQ == 19);
 
 /* Represents the metadata fields of struct flow. */
 struct flow_metadata {
@@ -125,6 +128,17 @@ void flow_extract(struct ofpbuf *, uint32_t priority, uint32_t mark,
                   const struct flow_tnl *, uint16_t in_port, struct flow *);
 void flow_extract_l3_onwards(struct ofpbuf *, struct flow *,
                              ovs_be16 dl_type);
+
+/* Returns the innermost dl_type.
+ * If there's an outer and an inner type then the inner type is returned.
+ * Otherwise, if there is only one type then it is returned. */
+static inline ovs_be16 flow_innermost_dl_type(const struct flow *flow)
+{
+    return flow->encap_dl_type == htons(0)
+        ? flow->dl_type
+        : flow->encap_dl_type;
+}
+
 void flow_zero_wildcards(struct flow *, const struct flow_wildcards *);
 void flow_get_metadata(const struct flow *, struct flow_metadata *);
 
@@ -142,6 +156,11 @@ 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_ttl(struct flow *flow, uint8_t ttl);
+void flow_set_mpls_tc(struct flow *flow, uint8_t tc);
+void flow_set_mpls_bos(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 f1bf63c..be7f643 100644
--- a/lib/match.c
+++ b/lib/match.c
@@ -461,6 +461,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_bos(struct match *match)
+{
+    match->wc.masks.mpls_lse &= ~htonl(MPLS_BOS_MASK);
+    flow_set_mpls_bos(&match->flow, 0);
+}
+
+/* Modifies 'match' so that it matches only packets with an MPLS header whose
+ * Stack Flag equals the lower bit of 'mpls_bos' */
+void
+match_set_mpls_bos(struct match *match, uint8_t mpls_bos)
+{
+    match->wc.masks.mpls_lse |= htonl(MPLS_BOS_MASK);
+    flow_set_mpls_bos(&match->flow, mpls_bos);
+}
+
 void
 match_set_tp_src(struct match *match, ovs_be16 tp_src)
 {
@@ -768,6 +819,24 @@ format_flow_tunnel(struct ds *s, const struct match *match)
     }
 }
 
+static void
+flow_format_mpls(const struct flow *flow, struct ds *s)
+{
+    if (flow->dl_type == htons(ETH_TYPE_MPLS)) {
+        ds_put_cstr(s, "mpls");
+    } else if (flow->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
+        ds_put_cstr(s, "mplsm");
+    } else {
+        return;
+    }
+
+    ds_put_format(s, "(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));
+}
+
 /* Appends a string representation of 'match' to 's'.  If 'priority' is
  * different from OFP_DEFAULT_PRIORITY, includes it in 's'. */
 void
@@ -781,7 +850,7 @@ match_format(const struct match *match, struct ds *s, unsigned int priority)
 
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 18);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 19);
 
     if (priority != OFP_DEFAULT_PRIORITY) {
         ds_put_format(s, "priority=%u,", priority);
@@ -833,6 +902,8 @@ match_format(const struct match *match, struct ds *s, unsigned int priority)
             ds_put_cstr(s, "arp,");
         } else if (f->dl_type == htons(ETH_TYPE_RARP)) {
             ds_put_cstr(s, "rarp,");
+        } else if (f->mpls_depth) {
+            flow_format_mpls(f, s);
         } else {
             skip_type = false;
         }
@@ -941,6 +1012,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_bos=%"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 ff0b5f2..d435aa4 100644
--- a/lib/match.h
+++ b/lib/match.h
@@ -78,6 +78,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_bos(struct match *);
+void match_set_mpls_bos(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 87887a8..0a59fb7 100644
--- a/lib/meta-flow.c
+++ b/lib/meta-flow.c
@@ -256,6 +256,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,
+        OXM_OF_MPLS_LABEL, "OXM_OF_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,
+        OXM_OF_MPLS_TC, "OXM_OF_MPLS_TC",
+        OXM_OF_MPLS_TC, "OXM_OF_MPLS_TC",
+    }, {
+        MFF_MPLS_BOS, "mpls_bos", NULL,
+        1, 1,
+        MFM_NONE,
+        MFS_DECIMAL,
+        MFP_MPLS,
+        false,
+        OXM_OF_MPLS_BOS, "OXM_OF_MPLS_BOS",
+        OXM_OF_MPLS_BOS, "OXM_OF_MPLS_BOS",
+    },
+
     /* ## -- ## */
     /* ## L3 ## */
     /* ## -- ## */
@@ -678,6 +710,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:
@@ -798,6 +837,8 @@ 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 eth_type_mpls(flow->dl_type);
     case MFP_IP_ANY:
         return is_ip_any(flow);
 
@@ -902,6 +943,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 & ~(MPLS_TC_MASK >> MPLS_TC_SHIFT));
+
+    case MFF_MPLS_BOS:
+        return !(value->u8 & ~(MPLS_BOS_MASK >> MPLS_BOS_SHIFT));
+
     case MFF_N_IDS:
     default:
         NOT_REACHED();
@@ -982,6 +1032,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;
@@ -1149,6 +1211,18 @@ mf_set_value(const struct mf_field *mf,
         match_set_dl_vlan_pcp(match, value->u8);
         break;
 
+    case MFF_MPLS_LABEL:
+        match_set_mpls_label(match, value->be32);
+        break;
+
+    case MFF_MPLS_TC:
+        match_set_mpls_tc(match, value->u8);
+        break;
+
+    case MFF_MPLS_BOS:
+        match_set_mpls_bos(match, value->u8);
+        break;
+
     case MFF_IPV4_SRC:
         match_set_nw_src(match, value->be32);
         break;
@@ -1316,6 +1390,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_bos(flow, value->u8);
+        break;
+
     case MFF_IPV4_SRC:
         flow->nw_src = value->be32;
         break;
@@ -1501,6 +1587,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_bos(match);
+        break;
+
     case MFF_IPV4_SRC:
     case MFF_ARP_SPA:
         match_set_nw_src_masked(match, htonl(0), htonl(0));
@@ -1628,6 +1726,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:
@@ -1885,6 +1986,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->be32 &= htonl(MPLS_TC_MASK >> MPLS_TC_SHIFT);
+        break;
+
+    case MFF_MPLS_BOS:
+        value->be32 &= htonl(MPLS_BOS_MASK >> MPLS_BOS_SHIFT);
+        break;
+
     case MFF_N_IDS:
     default:
         NOT_REACHED();
diff --git a/lib/meta-flow.h b/lib/meta-flow.h
index 3675883..57f6df5 100644
--- a/lib/meta-flow.h
+++ b/lib/meta-flow.h
@@ -78,6 +78,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 */
@@ -168,6 +173,9 @@ enum mf_prereqs {
     MFP_IPV6,
     MFP_IP_ANY,
 
+    /* L2.5 requirements. */
+    MFP_MPLS,
+
     /* L2+L3 requirements. */
     MFP_TCP,                    /* On IPv4 or IPv6. */
     MFP_UDP,                    /* On IPv4 or IPv6. */
@@ -220,6 +228,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_bos"   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 4d7fcd6..4ff516e 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -573,7 +573,7 @@ nx_put_raw(struct ofpbuf *b, bool oxm, const struct match *match,
     int match_len;
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 18);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 19);
 
     /* Metadata. */
     if (match->wc.masks.in_port) {
@@ -615,6 +615,22 @@ nx_put_raw(struct ofpbuf *b, bool oxm, const struct match *match,
                     match->wc.masks.vlan_tci);
     }
 
+    /* MPLS. */
+    if (eth_type_mpls(flow->dl_type)) {
+        if (match->wc.masks.mpls_lse & htonl(MPLS_TC_MASK)) {
+            nxm_put_8(b, OXM_OF_MPLS_TC, mpls_lse_to_tc(flow->mpls_lse));
+        }
+
+        if (match->wc.masks.mpls_lse & htonl(MPLS_BOS_MASK)) {
+            nxm_put_8(b, OXM_OF_MPLS_BOS, mpls_lse_to_bos(flow->mpls_lse));
+        }
+
+        if (match->wc.masks.mpls_lse & htonl(MPLS_LABEL_MASK)) {
+            nxm_put_32(b, OXM_OF_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 e2f21da..025664c 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -73,6 +73,8 @@ odp_action_len(uint16_t type)
     case OVS_ACTION_ATTR_USERSPACE: return -2;
     case OVS_ACTION_ATTR_PUSH_VLAN: return sizeof(struct ovs_action_push_vlan);
     case OVS_ACTION_ATTR_POP_VLAN: return 0;
+    case OVS_ACTION_ATTR_PUSH_MPLS: return sizeof(struct ovs_action_push_mpls);
+    case OVS_ACTION_ATTR_POP_MPLS: return sizeof(ovs_be16);
     case OVS_ACTION_ATTR_SET: return -2;
     case OVS_ACTION_ATTR_SAMPLE: return -2;
 
@@ -108,6 +110,7 @@ ovs_key_attr_to_string(enum ovs_key_attr attr)
     case OVS_KEY_ATTR_ICMPV6: return "icmpv6";
     case OVS_KEY_ATTR_ARP: return "arp";
     case OVS_KEY_ATTR_ND: return "nd";
+    case OVS_KEY_ATTR_MPLS: return "mpls";
 
     case __OVS_KEY_ATTR_MAX:
     default:
@@ -301,6 +304,16 @@ format_vlan_tci(struct ds *ds, ovs_be16 vlan_tci)
 }
 
 static void
+format_mpls_lse(struct ds *ds, ovs_be32 mpls_lse)
+{
+    ds_put_format(ds, "label=%"PRIu32",tc=%d,ttl=%d,bos=%d",
+                  mpls_lse_to_label(mpls_lse),
+                  mpls_lse_to_tc(mpls_lse),
+                  mpls_lse_to_ttl(mpls_lse),
+                  mpls_lse_to_bos(mpls_lse));
+}
+
+static void
 format_odp_action(struct ds *ds, const struct nlattr *a)
 {
     int expected_len;
@@ -339,6 +352,18 @@ format_odp_action(struct ds *ds, const struct nlattr *a)
     case OVS_ACTION_ATTR_POP_VLAN:
         ds_put_cstr(ds, "pop_vlan");
         break;
+    case OVS_ACTION_ATTR_PUSH_MPLS: {
+        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));
+        break;
+    }
+    case OVS_ACTION_ATTR_POP_MPLS: {
+        ovs_be16 ethertype = nl_attr_get_be16(a);
+        ds_put_format(ds, "pop_mpls(eth_type=0x%"PRIx16")", ntohs(ethertype));
+        break;
+    }
     case OVS_ACTION_ATTR_SAMPLE:
         format_odp_sample_action(ds, a);
         break;
@@ -622,6 +647,7 @@ odp_flow_key_attr_len(uint16_t type)
     case OVS_KEY_ATTR_ETHERNET: return sizeof(struct ovs_key_ethernet);
     case OVS_KEY_ATTR_VLAN: return sizeof(ovs_be16);
     case OVS_KEY_ATTR_ETHERTYPE: return 2;
+    case OVS_KEY_ATTR_MPLS: return sizeof(struct ovs_key_mpls);
     case OVS_KEY_ATTR_IPV4: return sizeof(struct ovs_key_ipv4);
     case OVS_KEY_ATTR_IPV6: return sizeof(struct ovs_key_ipv6);
     case OVS_KEY_ATTR_TCP: return sizeof(struct ovs_key_tcp);
@@ -765,6 +791,14 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds)
         ds_put_char(ds, ')');
         break;
 
+    case OVS_KEY_ATTR_MPLS: {
+        const struct ovs_key_mpls *mpls_key = nl_attr_get(a);
+        ds_put_char(ds, '(');
+        format_mpls_lse(ds, mpls_key->mpls_top_label);
+        ds_put_char(ds, ')');
+        break;
+    }
+
     case OVS_KEY_ATTR_ETHERTYPE:
         ds_put_format(ds, "(0x%04"PRIx16")",
                       ntohs(nl_attr_get_be16(a)));
@@ -925,6 +959,15 @@ ovs_frag_type_from_string(const char *s, enum ovs_frag_type *type)
     return true;
 }
 
+static ovs_be32
+mpls_lse_from_components(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 int
 parse_odp_key_attr(const char *s, const struct simap *port_names,
                    struct ofpbuf *key)
@@ -1080,6 +1123,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 = mpls_lse_from_components(label, tc, ttl, bos);
+            return n;
+        }
+    }
+
+    {
         ovs_be32 ipv4_src;
         ovs_be32 ipv4_dst;
         int ipv4_proto;
@@ -1466,6 +1525,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_depth) {
+        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)) {
@@ -1677,8 +1744,30 @@ parse_l3_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
                 const struct nlattr *key, size_t key_len)
 {
     static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+    ovs_be16 dl_type = flow->dl_type;
 
-    if (flow->dl_type == htons(ETH_TYPE_IP)) {
+    /* Parse MPLS label stack entry */
+    if (eth_type_mpls(flow->dl_type)) {
+        /* Calculate fitness of outer attributes. */
+        expected_attrs |= (UINT64_C(1) << OVS_KEY_ATTR_MPLS);
+
+        /* Get the MPLS LSE value. */
+        if (!(present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_MPLS))) {
+            return ODP_FIT_TOO_LITTLE;
+        }
+        flow->mpls_lse = nl_attr_get_be32(attrs[OVS_KEY_ATTR_MPLS]);
+        flow->mpls_depth++;
+
+        if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_IPV4)) {
+            dl_type = flow->encap_dl_type = htons(ETH_TYPE_IP);
+        } else if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_IPV6)) {
+            dl_type = flow->encap_dl_type = htons(ETH_TYPE_IPV6);
+        } else if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_ARP)) {
+            dl_type = flow->encap_dl_type = htons(ETH_TYPE_ARP);
+        }
+    }
+
+    if (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;
@@ -1693,7 +1782,7 @@ parse_l3_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
                 return ODP_FIT_ERROR;
             }
         }
-    } else if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
+    } else if (dl_type == htons(ETH_TYPE_IPV6)) {
         expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_IPV6;
         if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_IPV6)) {
             const struct ovs_key_ipv6 *ipv6_key;
@@ -1709,8 +1798,8 @@ parse_l3_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
                 return ODP_FIT_ERROR;
             }
         }
-    } else if (flow->dl_type == htons(ETH_TYPE_ARP) ||
-               flow->dl_type == htons(ETH_TYPE_RARP)) {
+    } else if (dl_type == htons(ETH_TYPE_ARP) ||
+               dl_type == htons(ETH_TYPE_RARP)) {
         expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_ARP;
         if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_ARP)) {
             const struct ovs_key_arp *arp_key;
@@ -1730,8 +1819,8 @@ parse_l3_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
     }
 
     if (flow->nw_proto == IPPROTO_TCP
-        && (flow->dl_type == htons(ETH_TYPE_IP) ||
-            flow->dl_type == htons(ETH_TYPE_IPV6))
+        && (dl_type == htons(ETH_TYPE_IP) ||
+            dl_type == htons(ETH_TYPE_IPV6))
         && !(flow->nw_frag & FLOW_NW_FRAG_LATER)) {
         expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_TCP;
         if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_TCP)) {
@@ -1742,8 +1831,8 @@ parse_l3_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
             flow->tp_dst = tcp_key->tcp_dst;
         }
     } else if (flow->nw_proto == IPPROTO_UDP
-               && (flow->dl_type == htons(ETH_TYPE_IP) ||
-                   flow->dl_type == htons(ETH_TYPE_IPV6))
+               && (dl_type == htons(ETH_TYPE_IP) ||
+                   dl_type == htons(ETH_TYPE_IPV6))
                && !(flow->nw_frag & FLOW_NW_FRAG_LATER)) {
         expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_UDP;
         if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_UDP)) {
@@ -1754,7 +1843,7 @@ parse_l3_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
             flow->tp_dst = udp_key->udp_dst;
         }
     } else if (flow->nw_proto == IPPROTO_ICMP
-               && flow->dl_type == htons(ETH_TYPE_IP)
+               && dl_type == htons(ETH_TYPE_IP)
                && !(flow->nw_frag & FLOW_NW_FRAG_LATER)) {
         expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_ICMP;
         if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_ICMP)) {
@@ -1765,7 +1854,7 @@ parse_l3_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
             flow->tp_dst = htons(icmp_key->icmp_code);
         }
     } else if (flow->nw_proto == IPPROTO_ICMPV6
-               && flow->dl_type == htons(ETH_TYPE_IPV6)
+               && dl_type == htons(ETH_TYPE_IPV6)
                && !(flow->nw_frag & FLOW_NW_FRAG_LATER)) {
         expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_ICMPV6;
         if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_ICMPV6)) {
@@ -2080,6 +2169,49 @@ 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 &&
+        flow->mpls_depth == base->mpls_depth) {
+        return;
+    }
+
+    if (flow->mpls_depth < base->mpls_depth) {
+        if (base->mpls_depth - flow->mpls_depth > 1) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 10);
+            VLOG_WARN_RL(&rl, "Multiple mpls_pop actions reduced to "
+                         " a single mpls_pop action");
+        }
+
+        nl_msg_put_be16(odp_actions, OVS_ACTION_ATTR_POP_MPLS, flow->dl_type);
+    } else if (flow->mpls_depth > base->mpls_depth) {
+        struct ovs_action_push_mpls *mpls;
+
+        if (flow->mpls_depth - base->mpls_depth > 1) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 10);
+            VLOG_WARN_RL(&rl, "Multiple mpls_push actions reduced to "
+                         " a single mpls_push action");
+        }
+
+        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 {
+        struct ovs_key_mpls mpls_key = { .mpls_top_label = flow->mpls_lse };
+
+        commit_set_action(odp_actions, OVS_KEY_ATTR_MPLS,
+                          &mpls_key, sizeof(mpls_key));
+    }
+
+    base->dl_type = flow->dl_type;
+    base->mpls_lse = flow->mpls_lse;
+    base->mpls_depth = flow->mpls_depth;
+}
+
+static void
 commit_set_ipv4_action(const struct flow *flow, struct flow *base,
                      struct ofpbuf *odp_actions)
 {
@@ -2133,7 +2265,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)
@@ -2218,6 +2349,7 @@ commit_odp_actions(const struct flow *flow, struct flow *base,
     commit_set_tunnel_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 a439d13..660d4e3 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -402,6 +402,24 @@ 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_mpls *nxapm = (struct nx_action_push *)a;
+        if (!eth_type_mpls(nxapm->ethertype)) {
+            return OFPERR_OFPBAC_BAD_ARGUMENT;
+        }
+        ofpact_put_PUSH_MPLS(out)->ethertype = nxapm->ethertype;
+        break;
+    }
+
+    case OFPUTIL_NXAST_POP_MPLS: {
+        struct nx_action_pop_mpls *nxapm = (struct nx_action_pop_mpls *)a;
+        if (eth_type_mpls(nxapm->ethertype)) {
+            return OFPERR_OFPBAC_BAD_ARGUMENT;
+        }
+        ofpact_put_POP_MPLS(out)->ethertype = nxapm->ethertype;
+        break;
+    }
     }
 
     return error;
@@ -774,6 +792,24 @@ 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;
+        if (!eth_type_mpls(oap->ethertype)) {
+            return OFPERR_OFPBAC_BAD_ARGUMENT;
+        }
+        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;
+        if (eth_type_mpls(oapm->ethertype)) {
+            return OFPERR_OFPBAC_BAD_ARGUMENT;
+        }
+        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);
@@ -1053,7 +1089,8 @@ exit:
 }
 
 static enum ofperr
-ofpact_check__(const struct ofpact *a, const struct flow *flow, int max_ports)
+ofpact_check__(const struct ofpact *a, const struct flow *flow, int max_ports,
+               ovs_be16 *dl_type)
 {
     const struct ofpact_enqueue *enqueue;
 
@@ -1096,7 +1133,13 @@ ofpact_check__(const struct ofpact *a, const struct flow *flow, int max_ports)
         return nxm_reg_move_check(ofpact_get_REG_MOVE(a), flow);
 
     case OFPACT_REG_LOAD:
-        return nxm_reg_load_check(ofpact_get_REG_LOAD(a), flow);
+        if (*dl_type != flow->dl_type) {
+            struct flow updated_flow = *flow;
+            updated_flow.dl_type = *dl_type;
+            return nxm_reg_load_check(ofpact_get_REG_LOAD(a), &updated_flow);
+        } else {
+            return nxm_reg_load_check(ofpact_get_REG_LOAD(a), flow);
+        }
 
     case OFPACT_DEC_TTL:
     case OFPACT_SET_TUNNEL:
@@ -1119,6 +1162,14 @@ ofpact_check__(const struct ofpact *a, const struct flow *flow, int max_ports)
     case OFPACT_EXIT:
         return 0;
 
+    case OFPACT_PUSH_MPLS:
+        *dl_type = ofpact_get_PUSH_MPLS(a)->ethertype;
+        return 0;
+
+    case OFPACT_POP_MPLS:
+        *dl_type = ofpact_get_POP_MPLS(a)->ethertype;
+        return 0;
+
     case OFPACT_CLEAR_ACTIONS:
     case OFPACT_WRITE_METADATA:
     case OFPACT_GOTO_TABLE:
@@ -1137,9 +1188,11 @@ ofpacts_check(const struct ofpact ofpacts[], size_t ofpacts_len,
               const struct flow *flow, int max_ports)
 {
     const struct ofpact *a;
+    ovs_be16 dl_type = flow->dl_type;
 
     OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) {
-        enum ofperr error = ofpact_check__(a, flow, max_ports);
+        enum ofperr error = ofpact_check__(a, flow, max_ports,
+                                           &dl_type);
         if (error) {
             return error;
         }
@@ -1385,6 +1438,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:
@@ -1512,6 +1575,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;
     }
@@ -1636,6 +1701,17 @@ ofpact_to_openflow11(const struct ofpact *a, struct ofpbuf *out)
         /* OpenFlow 1.1 uses OFPIT_WRITE_METADATA to express this action. */
         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_CLEAR_ACTIONS:
     case OFPACT_GOTO_TABLE:
         NOT_REACHED();
@@ -1777,6 +1853,8 @@ 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:
     case OFPACT_CLEAR_ACTIONS:
     case OFPACT_GOTO_TABLE:
     default:
@@ -2048,6 +2126,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 e930986..fa307a1 100644
--- a/lib/ofp-actions.h
+++ b/lib/ofp-actions.h
@@ -71,6 +71,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_mpls,     ofpact)    \
+    DEFINE_OFPACT(POP_MPLS,        ofpact_pop_mpls,      ofpact)    \
                                                                     \
     /* Metadata. */                                                 \
     DEFINE_OFPACT(SET_TUNNEL,      ofpact_tunnel,        ofpact)    \
@@ -310,6 +312,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_MPLS */
+struct ofpact_push_mpls {
+    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 1d0ab85..464c73e 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -562,6 +562,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;
     }
 }
 
@@ -726,7 +738,9 @@ parse_protocol(const char *name, const struct protocol **p_out)
         { "tcp6", ETH_TYPE_IPV6, IPPROTO_TCP },
         { "udp6", ETH_TYPE_IPV6, IPPROTO_UDP },
         { "rarp", ETH_TYPE_RARP, 0},
-};
+        { "mpls", ETH_TYPE_MPLS, 0 },
+        { "mplsm", ETH_TYPE_MPLS_MCAST, 0 },
+    };
     const struct protocol *p;
 
     for (p = protocols; p < &protocols[ARRAY_SIZE(protocols)]; p++) {
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index b97180e..1a9ca0e 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -642,6 +642,10 @@ ofp10_match_to_string(const struct ofp10_match *om, int verbosity)
             ds_put_cstr(&f, "arp,");
         } else if (om->dl_type == htons(ETH_TYPE_RARP)){
             ds_put_cstr(&f, "rarp,");
+        } 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 c66cd40..cd74d85 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -85,7 +85,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 == 18);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 19);
 
     /* Initialize most of wc. */
     flow_wildcards_init_catchall(wc);
@@ -440,8 +440,7 @@ ofputil_match_from_ofp11_match(const struct ofp11_match *ofmatch,
         }
     }
 
-    if (match->flow.dl_type == htons(ETH_TYPE_MPLS) ||
-        match->flow.dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
+    if (eth_type_mpls(match->flow.dl_type)) {
         enum { OFPFW11_MPLS_ALL = OFPFW11_MPLS_LABEL | OFPFW11_MPLS_TC };
 
         if ((wc & OFPFW11_MPLS_ALL) != OFPFW11_MPLS_ALL) {
@@ -1060,7 +1059,7 @@ ofputil_usable_protocols(const struct match *match)
 {
     const struct flow_wildcards *wc = &match->wc;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 18);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 19);
 
     /* tunnel params other than tun_id can't be sent in a flow_mod */
     if (!tun_parms_fully_wildcarded(wc)) {
@@ -1152,6 +1151,26 @@ ofputil_usable_protocols(const struct match *match)
             | OFPUTIL_P_OF13_OXM;
     }
 
+    /* NXM and OF1.3+ support matching MPLS label */
+    /* Allow for OF1.2 as there doesn't seem to be a
+     * particularly good reason not to */
+    if (wc->masks.mpls_lse & htonl(MPLS_LABEL_MASK)) {
+        return OFPUTIL_P_OF10_NXM_ANY | OFPUTIL_P_OF12_OXM
+            | OFPUTIL_P_OF13_OXM;
+    }
+
+    /* NXM and OF1.1+ support matching MPLS TC */
+    if (wc->masks.mpls_lse & htonl(MPLS_TC_MASK)) {
+        return OFPUTIL_P_OF10_NXM_ANY | OFPUTIL_P_OF12_OXM
+            | OFPUTIL_P_OF13_OXM;
+    }
+
+    /* NXM and OF1.1+ support matching MPLS stack flag */
+    if (wc->masks.mpls_lse & htonl(MPLS_BOS_MASK)) {
+        return OFPUTIL_P_OF10_NXM_ANY | OFPUTIL_P_OF12_OXM
+            | OFPUTIL_P_OF13_OXM;
+    }
+
     /* Other formats can express this rule. */
     return OFPUTIL_P_ANY;
 }
@@ -4307,7 +4326,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;
@@ -4336,6 +4356,8 @@ ofputil_normalize_match__(struct match *match, bool may_log)
     } else if (match->flow.dl_type == htons(ETH_TYPE_ARP) ||
                match->flow.dl_type == htons(ETH_TYPE_RARP)) {
         may_match = MAY_NW_PROTO | MAY_NW_ADDR | MAY_ARP_SHA | MAY_ARP_THA;
+    } else if (eth_type_mpls(match->flow.dl_type)) {
+        may_match = MAY_MPLS;
     } else {
         may_match = 0;
     }
@@ -4368,6 +4390,10 @@ 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);
+        wc.masks.mpls_depth = 0;
+    }
 
     /* Log any changes. */
     if (!flow_wildcards_equal(&wc, &match->wc)) {
diff --git a/lib/ofp-util.def b/lib/ofp-util.def
index 6d08d8a..1a60194 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, NULL)
@@ -62,6 +64,8 @@ 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_WRITE_METADATA,  nx_action_write_metadata, 0,
              "write_metadata")
+NXAST_ACTION(NXAST_PUSH_MPLS,       nx_action_push_mpls,    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 6484ab3..fd10da3 100644
--- a/lib/ofpbuf.c
+++ b/lib/ofpbuf.c
@@ -29,7 +29,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;
 }
@@ -176,6 +176,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;
     }
@@ -295,6 +298,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 73dfcdc..8d10dc8 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -192,7 +192,8 @@ eth_push_vlan(struct ofpbuf *packet, ovs_be16 tci)
 
 /* Removes outermost VLAN header (if any is present) from 'packet'.
  *
- * 'packet->l2' must initially point to 'packet''s Ethernet header. */
+ * 'packet->l2_5' should initially point to 'packet''s outer-most MPLS header
+ * or may be NULL if there are no MPLS headers. */
 void
 eth_pop_vlan(struct ofpbuf *packet)
 {
@@ -211,6 +212,192 @@ eth_pop_vlan(struct ofpbuf *packet)
     }
 }
 
+/* Return depth of mpls stack.
+ *
+ * 'packet->l2' must initially point to 'packet''s Ethernet header. */
+uint16_t
+eth_mpls_depth(struct ofpbuf *packet)
+{
+    struct mpls_hdr *mh = packet->l2_5;
+    uint16_t depth = 1;
+
+    if (!mh) {
+        return 0;
+    }
+
+    while (packet->size >= ((char *)mh - (char *)packet->data) + sizeof *mh &&
+           !(mh->mpls_lse & htonl(MPLS_BOS_MASK))) {
+        mh++;
+        depth++;
+    }
+
+    return depth;
+}
+
+/* Get the pointer of the ethertype of the packet. */
+static ovs_be16 *
+get_ethtype_ptr(struct ofpbuf *packet)
+{
+    struct eth_header *eh = packet->data;
+
+    if (eh->eth_type == htons(ETH_TYPE_VLAN)) {
+        return (ovs_be16 *)((char *)(packet->l2_5 ? packet->l2_5 : packet->l3)) - 2;
+    } else {
+        return &eh->eth_type;
+    }
+}
+
+/* Set ethertype of the packet. */
+void
+set_ethertype(struct ofpbuf *packet, ovs_be16 eth_type)
+{
+    *get_ethtype_ptr(packet) = eth_type;
+}
+
+/* Get ethertype of the packet. */
+static ovs_be16
+get_ethertype(struct ofpbuf *packet)
+{
+    return *get_ethtype_ptr(packet);
+}
+
+/* Set MPLS tag time-to-live. */
+static void
+set_mpls_lse_ttl(ovs_be32 *tag, uint8_t ttl)
+{
+    *tag &= ~htonl(MPLS_TTL_MASK);
+    *tag |= htonl((ttl << MPLS_TTL_SHIFT) & MPLS_TTL_MASK);
+}
+
+/* Set MPLS tag traffic-class. */
+void
+set_mpls_lse_tc(ovs_be32 *tag, uint8_t tc)
+{
+    *tag &= ~htonl(MPLS_TC_MASK);
+    *tag |= htonl((tc << MPLS_TC_SHIFT) & MPLS_TC_MASK);
+}
+
+/* Set MPLS tag label. */
+void
+set_mpls_lse_label(ovs_be32 *tag, ovs_be32 label)
+{
+    *tag &= ~htonl(MPLS_LABEL_MASK);
+    *tag |= htonl((ntohl(label) << MPLS_LABEL_SHIFT) & MPLS_LABEL_MASK);
+}
+
+/* Set MPLS tag BoS bit. */
+void
+set_mpls_lse_bos(ovs_be32 *tag, uint8_t bos)
+{
+    *tag &= ~htonl(MPLS_BOS_MASK);
+    *tag |= htonl((bos << MPLS_BOS_SHIFT) & MPLS_BOS_MASK);
+}
+
+/* Set MPLS label, TC, ttl and BoS bit. */
+ovs_be32
+set_mpls_lse_values(uint8_t ttl, uint8_t tc, uint8_t bos, ovs_be32 label)
+{
+    ovs_be32 tag = htonl(0);
+    set_mpls_lse_ttl(&tag, ttl);;
+    set_mpls_lse_tc(&tag, tc);
+    set_mpls_lse_bos(&tag, bos);
+    set_mpls_lse_label(&tag, label);
+    return tag;
+}
+
+/* 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;
+}
+
+/* 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_mpls(eth_type)) {
+        /* Update mpls label stack entry. */
+        mh->mpls_lse = mpls_lse;
+    }
+}
+
+/* Push MPLS label stack entry onto packet. */
+void
+push_mpls(struct ofpbuf *packet, ovs_be16 ethtype, ovs_be32 lse)
+{
+    struct eth_header *eh = packet->data;
+    ovs_be16 eth_type = htons(0);
+    struct mpls_hdr mh;
+
+    if (packet->size < sizeof *eh || !eth_type_mpls(ethtype)) {
+        return;
+    }
+
+    /* Get the packet ether_type. */
+    eth_type = get_ethertype(packet);
+
+    if (!eth_type_mpls(eth_type)) {
+        /* Set ethtype and mpls label stack entry. */
+        set_ethertype(packet, ethtype);
+        packet->l2_5 = packet->l3;
+    }
+
+    /* Push new MPLS shim header onto packet. */
+    mh.mpls_lse = lse;
+    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_mpls(eth_type)) {
+        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->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 7e2d4e9..31c1369 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -140,6 +140,10 @@ 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 *);
 
+uint16_t eth_mpls_depth(struct ofpbuf *packet);
+
+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 +151,16 @@ 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_lse(struct ofpbuf *, ovs_be32 label);
+void push_mpls(struct ofpbuf *packet, ovs_be16 ethtype, ovs_be32 lse);
+void pop_mpls(struct ofpbuf *, ovs_be16 ethtype);
+
+void set_mpls_lse_tc(ovs_be32 *tag, uint8_t tc);
+void set_mpls_lse_label(ovs_be32 *tag, ovs_be32 label);
+void set_mpls_lse_bos(ovs_be32 *tag, uint8_t bos);
+ovs_be32 set_mpls_lse_values(uint8_t ttl, uint8_t tc, uint8_t bos,
+                             ovs_be32 label);
+
 /* Example:
  *
  * uint8_t mac[ETH_ADDR_LEN];
@@ -186,6 +200,12 @@ void eth_addr_bitand(const uint8_t src[ETH_ADDR_LEN],
 #define ETH_TYPE_MPLS          0x8847
 #define ETH_TYPE_MPLS_MCAST    0x8848
 
+static inline bool eth_type_mpls(ovs_be16 eth_type)
+{
+    return eth_type == htons(ETH_TYPE_MPLS) ||
+        eth_type == htons(ETH_TYPE_MPLS_MCAST);
+}
+
 /* Minimum value for an Ethernet type.  Values below this are IEEE 802.2 frame
  * lengths. */
 #define ETH_TYPE_MIN           0x600
@@ -272,6 +292,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 in host byte order */
+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 uint8_t
+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 uint8_t
+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 BoS bit  */
+static inline uint8_t
+mpls_lse_to_bos(ovs_be32 mpls_lse)
+{
+    return (mpls_lse & htonl(MPLS_BOS_MASK)) != 0;
+}
+
 #define IP_FMT "%"PRIu32".%"PRIu32".%"PRIu32".%"PRIu32
 #define IP_ARGS(ip)                             \
     ntohl(ip) >> 24,                            \
@@ -332,6 +422,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. */
@@ -432,6 +524,10 @@ 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)
+
 /* Example:
  *
  * char *string = "1 ::1 2";
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index b37b482..d000a4e 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -5399,6 +5399,8 @@ compose_output_action__(struct action_xlate_ctx *ctx, uint16_t ofp_port,
     const struct ofport_dpif *ofport = get_ofp_port(ctx->ofproto, ofp_port);
     uint32_t odp_port = ofp_port_to_odp_port(ctx->ofproto, ofp_port);
     ovs_be16 flow_vlan_tci = ctx->flow.vlan_tci;
+    ovs_be32 flow_mpls_lse = ctx->flow.mpls_lse;
+    uint16_t flow_mpls_depth = ctx->flow.mpls_depth;
     uint8_t flow_nw_tos = ctx->flow.nw_tos;
     struct priority_to_dscp *pdscp;
     uint32_t out_port;
@@ -5432,6 +5434,8 @@ 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.mpls_depth = flow_mpls_depth;
     ctx->flow.nw_tos = flow_nw_tos;
 }
 
@@ -5573,16 +5577,11 @@ execute_controller_action(struct action_xlate_ctx *ctx, int len,
 
     if (packet->l2 && packet->l3) {
         struct eth_header *eh;
+        uint16_t mpls_depth;
 
         eth_pop_vlan(packet);
         eh = packet->l2;
 
-        /* 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. */
-        ovs_assert(ntohs(eh->eth_type) < ETH_TYPE_MIN
-                   || eh->eth_type == ctx->flow.dl_type);
-
         memcpy(eh->eth_src, ctx->flow.dl_src, sizeof eh->eth_src);
         memcpy(eh->eth_dst, ctx->flow.dl_dst, sizeof eh->eth_dst);
 
@@ -5590,6 +5589,16 @@ execute_controller_action(struct action_xlate_ctx *ctx, int len,
             eth_push_vlan(packet, ctx->flow.vlan_tci);
         }
 
+        mpls_depth = eth_mpls_depth(packet);
+
+        if (mpls_depth < ctx->flow.mpls_depth) {
+            push_mpls(packet, ctx->flow.dl_type, ctx->flow.mpls_lse);
+        } else if (mpls_depth > ctx->flow.mpls_depth) {
+            pop_mpls(packet, ctx->flow.dl_type);
+        } else if (mpls_depth) {
+            set_mpls_lse(packet, ctx->flow.mpls_lse);
+        }
+
         if (packet->l4) {
             if (ctx->flow.dl_type == htons(ETH_TYPE_IP)) {
                 packet_set_ipv4(packet, ctx->flow.nw_src, ctx->flow.nw_dst,
@@ -5622,6 +5631,53 @@ 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)
+{
+    ovs_assert(eth_type_mpls(eth_type));
+
+    if (ctx->base_flow.mpls_depth) {
+        ctx->flow.mpls_lse = ctx->base_flow.mpls_lse;
+        ctx->flow.mpls_lse &= ~htonl(MPLS_BOS_MASK);
+        ctx->flow.mpls_depth++;
+    } else {
+        ovs_be32 label;
+        uint8_t tc, ttl;
+
+        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)) {
+            label = htonl(0x2); /* IPV6 Explicit Null. */
+        } else {
+            label = htonl(0x0); /* IPV4 Explicit Null. */
+        }
+        tc = (ctx->flow.nw_tos & IP_DSCP_MASK) >> 2;
+        ttl = ctx->flow.nw_ttl ? ctx->flow.nw_ttl : 0x40;
+        ctx->flow.mpls_lse = set_mpls_lse_values(ttl, tc, 1, label);
+        ctx->flow.encap_dl_type = ctx->flow.dl_type;
+        ctx->flow.mpls_depth = 1;
+    }
+    ctx->flow.dl_type = eth_type;
+}
+
+static void
+compose_mpls_pop_action(struct action_xlate_ctx *ctx, ovs_be16 eth_type)
+{
+    ovs_assert(eth_type_mpls(ctx->flow.dl_type));
+    ovs_assert(!eth_type_mpls(eth_type));
+
+    nl_msg_put_be16(ctx->odp_actions, OVS_ACTION_ATTR_POP_MPLS, eth_type);
+
+    if (ctx->flow.mpls_depth) {
+        ctx->flow.mpls_depth--;
+        ctx->base_flow.mpls_depth--;
+        if (!ctx->flow.mpls_depth) {
+            ctx->flow.dl_type = eth_type;
+            ctx->flow.encap_dl_type = htons(0);
+        }
+    }
+}
+
 static bool
 compose_dec_ttl(struct action_xlate_ctx *ctx, struct ofpact_cnt_ids *ids)
 {
@@ -6008,6 +6064,14 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             nxm_execute_reg_load(ofpact_get_REG_LOAD(a), &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);
+            break;
+
         case OFPACT_DEC_TTL:
             if (compose_dec_ttl(ctx, ofpact_get_DEC_TTL(a))) {
                 goto out;
diff --git a/tests/odp.at b/tests/odp.at
index 009ac36..740c245 100644
--- a/tests/odp.at
+++ b/tests/odp.at
@@ -25,6 +25,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,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)
 skb_mark(0x1234),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(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.'
@@ -40,6 +45,14 @@ skb_mark(0x1234),in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth
 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/^/skb_priority(0x1234),/' odp-base.txt
 
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index fd66d24..eb41da8 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -249,6 +249,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->OXM_OF_MPLS_LABEL[[]],load:3->OXM_OF_MPLS_TC[[]],controller
+cookie=0xb dl_src=50:55:55:55:55:55 dl_type=0x8847 actions=load:1000->OXM_OF_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->OXM_OF_MPLS_LABEL[[]],load:7->OXM_OF_MPLS_TC[[]],controller
 ])
 AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
 
@@ -309,6 +313,83 @@ OFPT_PACKET_IN (xid=0x0): total_len=64 in_port=1 (via action) data_len=64 (unbuf
 tcp,metadata=0,in_port=0,dl_vlan=15,dl_vlan_pcp=0,dl_src=30:33:33:33:33:33,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=8,tp_dst=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)
+mpls(label:10,tc:3,ttl:64,bos:1),metadata=0,in_port=0,vlan_tci=0x0000,dl_src=40:44:44:44:44:44,dl_dst=50:54:00:00:00:07
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xa total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+mpls(label:10,tc:3,ttl:64,bos:1),metadata=0,in_port=0,vlan_tci=0x0000,dl_src=40:44:44:44:44:44,dl_dst=50:54:00:00:00:07
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xa total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+mpls(label:10,tc:3,ttl:64,bos:1),metadata=0,in_port=0,vlan_tci=0x0000,dl_src=40:44:44:44:44:44,dl_dst=50:54:00:00:00:07
+])
+
+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=60 in_port=1 (via action) data_len=60 (unbuffered)
+mpls(label:1000,tc:7,ttl:64,bos:1),metadata=0,in_port=0,vlan_tci=0x0000,dl_src=50:55:55:55:55:55,dl_dst=50:54:00:00:00:07
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xb total_len=60 in_port=1 (via action) data_len=60 (unbuffered)
+mpls(label:1000,tc:7,ttl:64,bos:1),metadata=0,in_port=0,vlan_tci=0x0000,dl_src=50:55:55:55:55:55,dl_dst=50:54:00:00:00:07
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xb total_len=60 in_port=1 (via action) data_len=60 (unbuffered)
+mpls(label:1000,tc:7,ttl:64,bos:1),metadata=0,in_port=0,vlan_tci=0x0000,dl_src=50:55:55:55:55:55,dl_dst=50:54:00:00:00:07
+])
+
+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)
+mplsm(label:1000,tc:7,ttl:64,bos:1),metadata=0,in_port=0,vlan_tci=0x0000,dl_src=70:77:77:77:77:77,dl_dst=50:54:00:00:00:07
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xc total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+mplsm(label:1000,tc:7,ttl:64,bos:1),metadata=0,in_port=0,vlan_tci=0x0000,dl_src=70:77:77:77:77:77,dl_dst=50:54:00:00:00:07
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xc total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+mplsm(label:1000,tc:7,ttl:64,bos:1),metadata=0,in_port=0,vlan_tci=0x0000,dl_src=70:77:77:77:77:77,dl_dst=50:54:00:00:00:07
+])
+
+
+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)
+tcp,metadata=0,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:66:66,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=64 tcp_csum:0
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=56 in_port=1 (via action) data_len=56 (unbuffered)
+tcp,metadata=0,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:66:66,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=64 tcp_csum:0
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=56 in_port=1 (via action) data_len=56 (unbuffered)
+tcp,metadata=0,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:66:66,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=64 tcp_csum:0
+])
+
 dnl Checksum TCP.
 AT_CHECK([ovs-ofctl monitor br0 65534 -P nxm --detach --no-chdir --pidfile 2> ofctl_monitor.log])
 
@@ -393,6 +474,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->OXM_OF_MPLS_LABEL[[]],load:0x3->OXM_OF_MPLS_TC[[]],CONTROLLER:65535
+ cookie=0xb, n_packets=3, n_bytes=180, dl_src=50:55:55:55:55:55,dl_type=0x8847 actions=load:0x3e8->OXM_OF_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->OXM_OF_MPLS_LABEL[[]],load:0x7->OXM_OF_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/test-bundle.c b/tests/test-bundle.c
index aa8b6f0..f5b24b4 100644
--- a/tests/test-bundle.c
+++ b/tests/test-bundle.c
@@ -137,6 +137,7 @@ main(int argc, char *argv[])
     for (i = 0; i < N_FLOWS; i++) {
         random_bytes(&flows[i], sizeof flows[i]);
         memset(flows[i].zeros, 0, sizeof flows[i].zeros);
+        flows[i].mpls_depth = 0;
         flows[i].regs[0] = OFPP_NONE;
     }
 
diff --git a/tests/test-multipath.c b/tests/test-multipath.c
index b990c13..8442bc2 100644
--- a/tests/test-multipath.c
+++ b/tests/test-multipath.c
@@ -61,6 +61,7 @@ main(int argc, char *argv[])
 
             random_bytes(&flow, sizeof flow);
             memset(flow.zeros, 0, sizeof flow.zeros);
+            flow.mpls_depth = 0;
 
             mp.max_link = n - 1;
             multipath_execute(&mp, &flow);
diff --git a/utilities/ovs-dpctl.c b/utilities/ovs-dpctl.c
index b48b349..363485b 100644
--- a/utilities/ovs-dpctl.c
+++ b/utilities/ovs-dpctl.c
@@ -939,14 +939,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;
@@ -980,6 +979,15 @@ dpctl_normalize_actions(int argc, char *argv[])
             printf("no vlan: ");
         }
 
+        if (af->flow.mpls_depth) {
+            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 c48645a..8b06c88 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -914,6 +914,26 @@ Push a new VLAN tag onto the packet.  Ethertype is used as the the Ethertype
 for the tag. Only ethertype 0x8100 should be used. (0x88a8 which the spec
 allows isn't supported at the moment.)
 A priority of zero and the tag of zero are used for the new tag.
+.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