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

Simon Horman horms at verge.net.au
Thu Oct 11 02:14:40 UTC 2012


On Tue, Oct 09, 2012 at 10:30:20AM -0700, Ben Pfaff wrote:
> The change to execute_set_action(), it implies that OVS_KEY_ATTR_MPLS
> got put in a place you don't want it in a previous patch:
> 
> parse_remaining_mpls() will read past the end of the packet.
> 
> I don't think that flow_extract() checks that the packet is long
> enough for an IPv4 or IPv6 header when it sets nw_ttl.
> 
> In flow_format(), I think it would be better to output nothing rather
> than mpls(0) if there is no MPLS header.
> 
> s/encapsualted/encapsulated/ in parse_mpls_onward().
> 
> In flow_compose(), I would normally format
>     inner_dl_type = flow->encap_dl_type == htons(0)
>         ?  flow->dl_type
>         : flow->encap_dl_type;
> as
>     inner_dl_type = (flow->encap_dl_type == htons(0)
>                      ? flow->dl_type
>                      : flow->encap_dl_type);
> 
> In flow_compose(), I think that the tests like this:
>     if (flow->dl_type == htons(ETH_TYPE_IP) ||
>         flow->encap_dl_type == htons(ETH_TYPE_IP)) {
> can be written to just check inner_dl_type instead.
> 
> In match_set_any_mpls_label(), I think that |= should be &=.
> Similarly for the other mpls_set_any_*() functions.
> 
> I no longer think that we should add NXM constants for the MPLS
> fields.  I think when I asked Ravi to do that, we didn't have OXM
> support.  Instead, let's just use the OXM constants (OXM_OF_MPLS_*).
> 
> I don't think that we should support writing to the MPLS BOS field,
> that is, 'writable' should be false for OXM_OF_MPLS_BOS.  We don't
> support writing to other fields that would essentially result in
> packet corruption or require us to reinterpret the flow on the fly
> (e.g. we don't support writing to the ethertype or IP protocol).
> 
> format_mpls_lse() shows a change in name of mpls_lse_to_stack() to
> mpls_lse_to_bos(), implying that this function's name was changed at
> some point.  It would be better to start out with the preferred name.
> 
> In format_mpls_lse_values(), we normally put a space after the
> 'return' keyword.  Also, despite its name, this function does not
> format anything (it's a parsing helper, not a formatting helper).
> 
> format_odp_action() makes a change in its output format (adding a
> comma) that seems should have been incorporated into an earlier patch.
> 
> In check_expectations_mpls(), please evaluate check_expectations() in
> a statement of its own, because MAX evaluates its arguments more than
> once.
> 
> (Or, we could write a function that simply returns MAX of two enum
> odp_key_fitness parameters.  We could call it
> greatest_expectations().)

!

> But, looking closer, the only value greater than ODP_FIT_TOO_LITTLE is
> ODP_FIT_ERROR, and check_expectations() never returns that, so in fact
> check_expectations_mpls() is a constant function that always returns
> ODP_FIT_TOO_LITTLE.

Furthermore, the datapath is now aware of MPLS in the current incantation
of the patchset. So rather I think that check_expectations_mpls() can just
be replaced by a call to check_expectations() as there is no longer any
need to upgrade the fitness to ODP_FIT_TOO_LITTLE. That was intended to
force use of the slow path (but its entirely likely that my logic was
broken anyway).

> Is there a circular calling relationship between parse_l3_onward() and
> parse_mpls_onward()?  Otherwise the stray prototype for
> parse_l3_onward seems a little odd.

Yes, there is a circular relationship. It could be factored out,
but the approach I took seemed the least messy option at the time.

> There's a change in parse_8021q_onward, in the assignment to
> encap_fitness, that I think just changes line breaking without making
> any semantic changes.
> 
> This change deletes a blank line before commit_set_nw_action() that
> it should not.
> 
> The checks that this patch adds to ofpact_check__() should happen at
> action parsing time instead, because this is not necessary to know the
> flow or the maximum number of ports to validate them.
> 
> The #if 0...#endif block should not get added to ofpact_format().
> 
> There are some typos in the comment on ofpact_push: "MPSL" => "MPLS",
> "BPP" => "PBB".
> 
> My impression is that 0x8847 is much more common than 0x8848, so
> perhaps that should be a default Ethertype for the "push_mpls" action.

I'm not sure that I understand.

Would you like to provide a version of push_mpls() that doesn't
have an Ethertype parameter. Or use 0x8847 if the Ethertype parameter
is zero. Or something else?

> 
> The comment in ofputil_usable_protocols() says that OF1.1+ supports
> matching on the MPLS stack flag, but in fact this was only added in
> OF1.3.  (OF1.1 supports the other MPLS matches.)
> 
> I left off reading this at packets.c, I'll try to get back to it
> later.

Below is a revised version of the patch which:

* Addresses the concerns you raise above, other
 than the default Ethertype for push_mpls()
* Incorporates the user-space datapath code as there
  seem to be mutual dependencies between that and
  the other user-space code.

I have pushed this and much more minor changes to the other
patches in the series to the devel/mpls branch of
git://github.com/horms/openvswitch.git
(HEAD = 8b8c4af22ba5c1bddd1dc455fd8a5820cc3e2812)

I would repost the entire series, but as you haven't completed
your review of this patch I thought it would be best to just
post an update inline.

----------------------------------------------------------------

From: Simon Horman <horms at verge.net.au>

User-Space MPLS actions and matches

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

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

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

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

This patch is based heavily on work by Ravi K.

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

---

v2.3 draft
* As suggested by Yamahata-san
  - Use MPLS_xxx_MASK >> MPLS_xxx_SHIFT in mf_is_value_valid() and
    mf_random_value()
* 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
* 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.

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/openflow/nicira-ext.h   |   24 +++
 include/openflow/openflow-1.2.h |    2 +
 lib/dpif-netdev.c               |   15 +-
 lib/flow.c                      |  116 ++++++++++++-
 lib/flow.h                      |   14 +-
 lib/match.c                     |   69 +++++++-
 lib/match.h                     |    6 +
 lib/meta-flow.c                 |  124 ++++++++++++++
 lib/meta-flow.h                 |    9 +
 lib/nx-match.c                  |   20 ++-
 lib/odp-util.c                  |  183 ++++++++++++++++++--
 lib/ofp-actions.c               |   77 +++++++++
 lib/ofp-actions.h               |   18 ++
 lib/ofp-parse.c                 |   14 ++
 lib/ofp-print.c                 |    4 +
 lib/ofp-util.c                  |   28 ++-
 lib/ofp-util.def                |    4 +
 lib/ofpbuf.c                    |    8 +-
 lib/ofpbuf.h                    |    1 +
 lib/packets.c                   |  356 +++++++++++++++++++++++++++++++++++++++
 lib/packets.h                   |   88 ++++++++++
 ofproto/ofproto-dpif.c          |   96 ++++++++++-
 tests/automake.mk               |    5 +
 tests/odp.at                    |   13 ++
 tests/ofproto-dpif.at           |   85 ++++++++++
 tests/test-mpls.c               |  288 +++++++++++++++++++++++++++++++
 utilities/ovs-dpctl.c           |   18 +-
 utilities/ovs-ofctl.8.in        |   21 +++
 28 files changed, 1663 insertions(+), 43 deletions(-)
 create mode 100644 tests/test-mpls.c

diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
index 75bf6db..c2b5d46 100644
--- a/include/openflow/nicira-ext.h
+++ b/include/openflow/nicira-ext.h
@@ -304,6 +304,8 @@ enum nx_action_subtype {
     NXAST_FIN_TIMEOUT,          /* struct nx_action_fin_timeout */
     NXAST_CONTROLLER,           /* struct nx_action_controller */
     NXAST_DEC_TTL_CNT_IDS,      /* struct nx_action_cnt_ids */
+    NXAST_PUSH_MPLS,            /* struct nx_action_push_mpls */
+    NXAST_POP_MPLS,             /* struct nx_action_pop_mpls */
 };
 
 /* Header for Nicira-defined actions. */
@@ -2192,4 +2194,26 @@ struct nx_flow_monitor_cancel {
 };
 OFP_ASSERT(sizeof(struct nx_flow_monitor_cancel) == 4);
 
+/* Action structure for NXAST_PUSH_VLAN/MPLS. */
+struct nx_action_push {
+    ovs_be16 type;                  /* OFPAT_PUSH_VLAN/MPLS. */
+    ovs_be16 len;                   /* Length is 8. */
+    ovs_be32 vendor;                /* NX_VENDOR_ID. */
+    ovs_be16 subtype;               /* NXAST_PUSH_MPLS. */
+    ovs_be16 ethertype;             /* Ethertype */
+    uint8_t  pad[4];
+};
+OFP_ASSERT(sizeof(struct nx_action_push) == 16);
+
+/* Action structure for NXAST_POP_MPLS. */
+struct nx_action_pop_mpls {
+    ovs_be16 type;                  /* OFPAT_POP_MPLS. */
+    ovs_be16 len;                   /* Length is 8. */
+    ovs_be32 vendor;                /* NX_VENDOR_ID. */
+    ovs_be16 subtype;               /* NXAST_POP_MPLS. */
+    ovs_be16 ethertype;             /* Ethertype */
+    uint8_t  pad[4];
+};
+OFP_ASSERT(sizeof(struct nx_action_pop_mpls) == 16);
+
 #endif /* openflow/nicira-ext.h */
diff --git a/include/openflow/openflow-1.2.h b/include/openflow/openflow-1.2.h
index 1c3f017..53e7f70 100644
--- a/include/openflow/openflow-1.2.h
+++ b/include/openflow/openflow-1.2.h
@@ -106,6 +106,7 @@ enum oxm12_ofb_match_fields {
     OFPXMT12_OFB_IPV6_ND_TLL,    /* Target link-layer for ND. */
     OFPXMT12_OFB_MPLS_LABEL,     /* MPLS label. */
     OFPXMT12_OFB_MPLS_TC,        /* MPLS TC. */
+    OFPXMT12_OFB_MPLS_BOS,       /* MPLS BoS. */
 
     /* End Marker */
     OFPXMT12_OFB_MAX,
@@ -172,6 +173,7 @@ enum oxm12_ofb_match_fields {
 #define OXM_OF_IPV6_ND_TLL    OXM_HEADER   (OFPXMT12_OFB_IPV6_ND_TLL, 6)
 #define OXM_OF_MPLS_LABEL     OXM_HEADER   (OFPXMT12_OFB_MPLS_LABEL, 4)
 #define OXM_OF_MPLS_TC        OXM_HEADER   (OFPXMT12_OFB_MPLS_TC, 1)
+#define OXM_OF_MPLS_BOS       OXM_HEADER   (OFPXMT12_OFB_MPLS_BOS, 1)
 
 /* The VLAN id is 12-bits, so we can use the entire 16 bits to indicate
  * special conditions.
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index 091607d..6923263 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -1250,6 +1250,18 @@ dp_netdev_execute_actions(struct dp_netdev *dp,
             eth_pop_vlan(packet);
             break;
 
+        case OVS_ACTION_ATTR_PUSH_MPLS:
+            push_mpls(packet, nl_attr_get_be16(a));
+            break;
+
+        case OVS_ACTION_ATTR_POP_MPLS:
+             pop_mpls(packet, nl_attr_get_be16(a));
+             break;
+
+        case OVS_ACTION_ATTR_SET_MPLS:
+             set_mpls_lse(packet, nl_attr_get_be32(a));
+             break;
+
         case OVS_ACTION_ATTR_SET:
             execute_set_action(packet, nl_attr_get(a));
             break;
@@ -1259,9 +1271,6 @@ dp_netdev_execute_actions(struct dp_netdev *dp,
             break;
 
         case OVS_ACTION_ATTR_UNSPEC:
-        case OVS_ACTION_ATTR_PUSH_MPLS:
-        case OVS_ACTION_ATTR_POP_MPLS:
-        case OVS_ACTION_ATTR_SET_MPLS:
         case __OVS_ACTION_ATTR_MAX:
             NOT_REACHED();
         }
diff --git a/lib/flow.c b/lib/flow.c
index 76d2340..5291e19 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -93,6 +93,28 @@ pull_icmpv6(struct ofpbuf *packet)
 }
 
 static void
+parse_remaining_mpls(struct ofpbuf *b)
+{
+    /* Proceed with parsing remaining MPLS headers. */
+    struct mpls_hdr *mh;
+    while ((mh = ofpbuf_try_pull(b, sizeof *mh)) &&
+           (mh->mpls_lse & htonl(MPLS_BOS_MASK))) {
+        ;
+    }
+}
+
+static void
+parse_mpls(struct ofpbuf *b, struct flow *flow)
+{
+    /* Make sure there is some data following MPLS header
+       before proceeding with parsing MPLS headers. */
+    if (b->size >= sizeof(struct mpls_hdr) + sizeof(ovs_be16)) {
+        struct mpls_hdr *mh = ofpbuf_pull(b, sizeof *mh);
+        flow->mpls_lse = mh->mpls_lse;
+    }
+}
+
+static void
 parse_vlan(struct ofpbuf *b, struct flow *flow)
 {
     struct qtag_prefix {
@@ -323,6 +345,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.
@@ -352,10 +376,11 @@ flow_extract(struct ofpbuf *packet, uint32_t skb_priority,
     flow->in_port = ofp_in_port;
     flow->skb_priority = skb_priority;
 
-    packet->l2 = b.data;
-    packet->l3 = NULL;
-    packet->l4 = NULL;
-    packet->l7 = NULL;
+    packet->l2   = b.data;
+    packet->l2_5 = NULL;
+    packet->l3   = NULL;
+    packet->l4   = NULL;
+    packet->l7   = NULL;
 
     if (b.size < sizeof *eth) {
         return;
@@ -373,6 +398,25 @@ flow_extract(struct ofpbuf *packet, uint32_t skb_priority,
     }
     flow->dl_type = parse_ethertype(&b);
 
+    /* Parse mpls, copy l3 ttl. */
+    if (flow->dl_type == htons(ETH_TYPE_MPLS) ||
+        flow->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
+        struct ip_header *ih = b.data;
+        struct ip6_hdr   *ih6 = b.data;
+        packet->l2_5 = b.data;
+        parse_mpls(&b, flow);
+        if (!(flow->mpls_lse & htonl(MPLS_BOS_MASK))) {
+            parse_remaining_mpls(&b);
+        }
+        if (packet->size >= sizeof *ih &&
+            IP_VER(ih->ip_ihl_ver) == IP_VERSION) {
+            flow->nw_ttl = ih->ip_ttl;
+        } else if (packet->size >= sizeof *ih6 &&
+                   IP6_VER(ih6->ip6_vfc) == IP6_VERSION) {
+            flow->nw_ttl = ih6->ip6_hlim;
+        }
+    }
+
     /* Network layer. */
     packet->l3 = b.data;
     if (flow->dl_type == htons(ETH_TYPE_IP)) {
@@ -463,7 +507,7 @@ flow_zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards)
 void
 flow_get_metadata(const struct flow *flow, struct flow_metadata *fmd)
 {
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 17);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 18);
 
     fmd->tun_id = flow->tunnel.tun_id;
     fmd->metadata = flow->metadata;
@@ -536,6 +580,16 @@ flow_format(struct ds *ds, const struct flow *flow)
                   ETH_ADDR_ARGS(flow->dl_dst),
                   ntohs(flow->dl_type));
 
+    if (flow->mpls_lse) {
+        ds_put_format(ds, ",mpls(");
+        ds_put_format(ds, "label:%"PRIu32",tc:%d,ttl:%d,bos:%d",
+                      mpls_lse_to_label(flow->mpls_lse),
+                      mpls_lse_to_tc(flow->mpls_lse),
+                      mpls_lse_to_ttl(flow->mpls_lse),
+                      mpls_lse_to_bos(flow->mpls_lse));
+        ds_put_char(ds, ')');
+    }
+
     if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
         ds_put_format(ds, " label:%#"PRIx32" proto:%"PRIu8" tos:%#"PRIx8
                           " ttl:%"PRIu8" ipv6(",
@@ -824,6 +878,39 @@ flow_set_vlan_pcp(struct flow *flow, uint8_t pcp)
     flow->vlan_tci |= htons((pcp << VLAN_PCP_SHIFT) | VLAN_CFI);
 }
 
+/* Sets the MPLS Label that 'flow' matches to 'label', which is interpreted
+ * as an OpenFlow 1.1 "mpls_label" value. */
+void
+flow_set_mpls_label(struct flow *flow, ovs_be32 label)
+{
+    if (label == htonl(0)) {
+        flow->mpls_lse = htonl(0);
+    } else {
+        flow->mpls_lse &= ~htonl(MPLS_LABEL_MASK);
+        flow->mpls_lse |=
+            htonl((ntohl(label) << MPLS_LABEL_SHIFT) & MPLS_LABEL_MASK);
+    }
+}
+
+/* Sets the MPLS TC that 'flow' matches to 'tc', which should be in the
+ * range 0...7. */
+void
+flow_set_mpls_tc(struct flow *flow, uint8_t tc)
+{
+    tc &= 0x07;
+    flow->mpls_lse &= ~htonl(MPLS_TC_MASK);
+    flow->mpls_lse |= htonl(tc << MPLS_TC_SHIFT);
+}
+
+/* Sets the MPLS BOS bit that 'flow' matches to which should be 0 or 1. */
+void
+flow_set_mpls_bos(struct flow *flow, uint8_t stack)
+{
+    stack &= 0x01;
+    flow->mpls_lse &= ~htonl(MPLS_BOS_MASK);
+    flow->mpls_lse |= htonl(stack << MPLS_BOS_SHIFT);
+}
+
 /* Puts into 'b' a packet that flow_extract() would parse as having the given
  * 'flow'.
  *
@@ -833,7 +920,12 @@ flow_set_vlan_pcp(struct flow *flow, uint8_t pcp)
 void
 flow_compose(struct ofpbuf *b, const struct flow *flow)
 {
-    eth_compose(b, flow->dl_dst, flow->dl_src, ntohs(flow->dl_type), 0);
+    ovs_be16 inner_dl_type;
+
+    inner_dl_type = (flow->encap_dl_type == htons(0)
+                     ? flow->dl_type
+                     : flow->encap_dl_type);
+    eth_compose(b, flow->dl_dst, flow->dl_src, ntohs(inner_dl_type), 0);
     if (flow->dl_type == htons(FLOW_DL_TYPE_NONE)) {
         struct eth_header *eth = b->l2;
         eth->eth_type = htons(b->size);
@@ -844,7 +936,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);
@@ -889,9 +981,9 @@ 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)) {
+    } else if (inner_dl_type == htons(ETH_TYPE_ARP)) {
         struct arp_eth_header *arp;
 
         b->l3 = arp = ofpbuf_put_zeros(b, sizeof *arp);
@@ -909,6 +1001,12 @@ flow_compose(struct ofpbuf *b, const struct flow *flow)
             memcpy(arp->ar_tha, flow->arp_tha, ETH_ADDR_LEN);
         }
     }
+
+    if (flow->dl_type == htons(ETH_TYPE_MPLS) ||
+        flow->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
+        push_mpls(b, flow->dl_type);
+        set_mpls_lse(b, flow->mpls_lse);
+    }
 }
 
 /* Compressed flow. */
diff --git a/lib/flow.h b/lib/flow.h
index 9388f20..dc02a89 100644
--- a/lib/flow.h
+++ b/lib/flow.h
@@ -36,7 +36,7 @@ struct ofpbuf;
 /* This sequence number should be incremented whenever anything involving flows
  * or the wildcarding of flows changes.  This will cause build assertion
  * failures in places which likely need to be updated. */
-#define FLOW_WC_SEQ 17
+#define FLOW_WC_SEQ 18
 
 #define FLOW_N_REGS 8
 BUILD_ASSERT_DECL(FLOW_N_REGS <= NXM_NX_MAX_REGS);
@@ -76,9 +76,11 @@ struct flow {
     ovs_be32 nw_src;            /* IPv4 source address. */
     ovs_be32 nw_dst;            /* IPv4 destination address. */
     ovs_be32 ipv6_label;        /* IPv6 flow label. */
+    ovs_be32 mpls_lse;          /* MPLS label stack entry. */
     uint16_t in_port;           /* OpenFlow port number of input port. */
     ovs_be16 vlan_tci;          /* If 802.1Q, TCI | VLAN_CFI; otherwise 0. */
     ovs_be16 dl_type;           /* Ethernet frame type. */
+    ovs_be16 encap_dl_type;     /* MPLS encapsulated Ethernet frame type */
     ovs_be16 tp_src;            /* TCP/UDP source port. */
     ovs_be16 tp_dst;            /* TCP/UDP destination port. */
     uint8_t dl_src[6];          /* Ethernet source address. */
@@ -89,15 +91,15 @@ struct flow {
     uint8_t arp_tha[6];         /* ARP/ND target hardware address. */
     uint8_t nw_ttl;             /* IP TTL/Hop Limit. */
     uint8_t nw_frag;            /* FLOW_FRAG_* flags. */
-    uint8_t zeros[2];           /* Must be zero. */
+    uint8_t zeros[4];           /* Must be zero. */
 };
 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) + 144 &&
-                  FLOW_WC_SEQ == 17);
+BUILD_ASSERT_DECL(sizeof(struct flow) == sizeof(struct flow_tnl) + 152 &&
+                  FLOW_WC_SEQ == 18);
 
 /* Represents the metadata fields of struct flow. */
 struct flow_metadata {
@@ -123,6 +125,10 @@ void flow_set_dl_vlan(struct flow *, ovs_be16 vid);
 void flow_set_vlan_vid(struct flow *, ovs_be16 vid);
 void flow_set_vlan_pcp(struct flow *, uint8_t pcp);
 
+void flow_set_mpls_label(struct flow *flow, ovs_be32 label);
+void flow_set_mpls_tc(struct flow *flow, uint8_t tc);
+void flow_set_mpls_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 b25569d..c28783d 100644
--- a/lib/match.c
+++ b/lib/match.c
@@ -284,6 +284,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)
 {
@@ -571,7 +622,7 @@ match_format(const struct match *match, struct ds *s, unsigned int priority)
 
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 17);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 18);
 
     if (priority != OFP_DEFAULT_PRIORITY) {
         ds_put_format(s, "priority=%u,", priority);
@@ -613,6 +664,10 @@ match_format(const struct match *match, struct ds *s, unsigned int priority)
             }
         } else if (f->dl_type == htons(ETH_TYPE_ARP)) {
             ds_put_cstr(s, "arp,");
+        } else if (f->dl_type == htons(ETH_TYPE_MPLS)) {
+            ds_put_cstr(s, "mpls,");
+        } else if (f->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
+            ds_put_cstr(s, "mplsm,");
         } else {
             skip_type = false;
         }
@@ -722,6 +777,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 2d05819..a468594 100644
--- a/lib/match.h
+++ b/lib/match.h
@@ -65,6 +65,12 @@ void match_set_vlan_vid(struct match *, ovs_be16);
 void match_set_vlan_vid_masked(struct match *, ovs_be16 vid, ovs_be16 mask);
 void match_set_any_pcp(struct match *);
 void match_set_dl_vlan_pcp(struct match *, uint8_t);
+void match_set_any_mpls_label(struct match *);
+void match_set_mpls_label(struct match *, ovs_be32);
+void match_set_any_mpls_tc(struct match *);
+void match_set_mpls_tc(struct match *, uint8_t);
+void match_set_any_mpls_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 8931523..0aceebd 100644
--- a/lib/meta-flow.c
+++ b/lib/meta-flow.c
@@ -194,6 +194,38 @@ static const struct mf_field mf_fields[MFF_N_IDS] = {
         OXM_OF_VLAN_PCP, "OXM_OF_VLAN_PCP",
     },
 
+    /* ## ---- ## */
+    /* ## L2.5 ## */
+    /* ## ---- ## */
+    {
+        MFF_MPLS_LABEL, "mpls_label", NULL,
+        4, 20,
+        MFM_NONE,
+        MFS_DECIMAL,
+        MFP_MPLS,
+        true,
+        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 ## */
     /* ## -- ## */
@@ -607,6 +639,13 @@ mf_is_all_wild(const struct mf_field *mf, const struct flow_wildcards *wc)
     case MFF_VLAN_PCP:
         return !(wc->masks.vlan_tci & htons(VLAN_PCP_MASK));
 
+    case MFF_MPLS_LABEL:
+        return !(wc->masks.mpls_lse & htonl(MPLS_LABEL_MASK));
+    case MFF_MPLS_TC:
+        return !(wc->masks.mpls_lse & htonl(MPLS_TC_MASK));
+    case MFF_MPLS_BOS:
+        return !(wc->masks.mpls_lse & htonl(MPLS_BOS_MASK));
+
     case MFF_IPV4_SRC:
         return !wc->masks.nw_src;
     case MFF_IPV4_DST:
@@ -707,6 +746,16 @@ mf_get_mask(const struct mf_field *mf, const struct flow_wildcards *wc,
         mask->u8 = vlan_tci_to_pcp(wc->masks.vlan_tci);
         break;
 
+    case MFF_MPLS_LABEL:
+        mask->be32 = htonl(mpls_lse_to_label(wc->masks.mpls_lse));
+        break;
+    case MFF_MPLS_TC:
+        mask->u8 = mpls_lse_to_tc(wc->masks.mpls_lse);
+        break;
+    case MFF_MPLS_BOS:
+        mask->u8 = mpls_lse_to_bos(wc->masks.mpls_lse);
+        break;
+
     case MFF_IPV4_SRC:
         mask->be32 = wc->masks.nw_src;
         break;
@@ -841,6 +890,9 @@ mf_are_prereqs_ok(const struct mf_field *mf, const struct flow *flow)
         return flow->dl_type == htons(ETH_TYPE_IPV6);
     case MFP_VLAN_VID:
         return (flow->vlan_tci & htons(VLAN_CFI)) != 0;
+    case MFP_MPLS:
+        return (flow->dl_type == htons(ETH_TYPE_MPLS) ||
+                flow->dl_type == htons(ETH_TYPE_MPLS_MCAST));
     case MFP_IP_ANY:
         return is_ip_any(flow);
 
@@ -938,6 +990,15 @@ mf_is_value_valid(const struct mf_field *mf, const union mf_value *value)
     case MFF_IPV6_LABEL:
         return !(value->be32 & ~htonl(IPV6_LABEL_MASK));
 
+    case MFF_MPLS_LABEL:
+        return !(value->be32 & ~htonl(MPLS_LABEL_MASK >> MPLS_LABEL_SHIFT));
+
+    case MFF_MPLS_TC:
+        return !(value->be32 & ~htonl(MPLS_TC_MASK >> MPLS_TC_SHIFT));
+
+    case MFF_MPLS_BOS:
+        return !(value->be32 & ~htonl(MPLS_BOS_MASK >> MPLS_BOS_SHIFT));
+
     case MFF_N_IDS:
     default:
         NOT_REACHED();
@@ -994,6 +1055,18 @@ mf_get_value(const struct mf_field *mf, const struct flow *flow,
         value->u8 = vlan_tci_to_pcp(flow->vlan_tci);
         break;
 
+    case MFF_MPLS_LABEL:
+        value->be32 = htonl(mpls_lse_to_label(flow->mpls_lse));
+        break;
+
+    case MFF_MPLS_TC:
+        value->u8 = mpls_lse_to_tc(flow->mpls_lse);
+        break;
+
+    case MFF_MPLS_BOS:
+        value->u8 = mpls_lse_to_bos(flow->mpls_lse);
+        break;
+
     case MFF_IPV4_SRC:
         value->be32 = flow->nw_src;
         break;
@@ -1137,6 +1210,18 @@ mf_set_value(const struct mf_field *mf,
         match_set_dl_vlan_pcp(match, value->u8);
         break;
 
+    case MFF_MPLS_TC:
+        match_set_mpls_tc(match, value->u8);
+        break;
+
+    case MFF_MPLS_BOS:
+        match_set_mpls_bos(match, value->u8);
+        break;
+
+    case MFF_MPLS_LABEL:
+        match_set_mpls_label(match, value->be32);
+        break;
+
     case MFF_IPV4_SRC:
         match_set_nw_src(match, value->be32);
         break;
@@ -1280,6 +1365,18 @@ mf_set_flow_value(const struct mf_field *mf,
         flow_set_vlan_pcp(flow, value->u8);
         break;
 
+    case MFF_MPLS_LABEL:
+        flow_set_mpls_label(flow, value->be32);
+        break;
+
+    case MFF_MPLS_TC:
+        flow_set_mpls_tc(flow, value->u8);
+        break;
+
+    case MFF_MPLS_BOS:
+        flow_set_mpls_bos(flow, value->u8);
+        break;
+
     case MFF_IPV4_SRC:
         flow->nw_src = value->be32;
         break;
@@ -1439,6 +1536,18 @@ mf_set_wild(const struct mf_field *mf, struct match *match)
         match_set_any_pcp(match);
         break;
 
+    case MFF_MPLS_LABEL:
+        match_set_any_mpls_label(match);
+        break;
+
+    case MFF_MPLS_TC:
+        match_set_any_mpls_tc(match);
+        break;
+
+    case MFF_MPLS_BOS:
+        match_set_any_mpls_bos(match);
+        break;
+
     case MFF_IPV4_SRC:
     case MFF_ARP_SPA:
         match_set_nw_src_masked(match, htonl(0), htonl(0));
@@ -1564,6 +1673,9 @@ mf_set(const struct mf_field *mf,
     case MFF_DL_VLAN:
     case MFF_DL_VLAN_PCP:
     case MFF_VLAN_PCP:
+    case MFF_MPLS_LABEL:
+    case MFF_MPLS_TC:
+    case MFF_MPLS_BOS:
     case MFF_IP_PROTO:
     case MFF_IP_TTL:
     case MFF_IP_DSCP:
@@ -1794,6 +1906,18 @@ mf_random_value(const struct mf_field *mf, union mf_value *value)
         value->u8 &= 0x07;
         break;
 
+    case MFF_MPLS_LABEL:
+        value->be32 &= htonl(MPLS_LABEL_MASK >> MPLS_LABEL_SHIFT);
+        break;
+
+    case MFF_MPLS_TC:
+        value->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 3fe9014..6e6a72f 100644
--- a/lib/meta-flow.h
+++ b/lib/meta-flow.h
@@ -71,6 +71,11 @@ enum mf_field_id {
     MFF_DL_VLAN_PCP,            /* u8 (OpenFlow 1.0 compatibility) */
     MFF_VLAN_PCP,               /* be16 (OpenFlow 1.2 compatibility) */
 
+    /* L2.5 */
+    MFF_MPLS_LABEL,             /* be32 */
+    MFF_MPLS_TC,                /* u8 */
+    MFF_MPLS_BOS,               /* u8 */
+
     /* L3. */
     MFF_IPV4_SRC,               /* be32 */
     MFF_IPV4_DST,               /* be32 */
@@ -157,6 +162,7 @@ enum mf_prereqs {
     /* L2 requirements. */
     MFP_ARP,
     MFP_VLAN_VID,
+    MFP_MPLS,
     MFP_IPV4,
     MFP_IPV6,
     MFP_IP_ANY,
@@ -212,6 +218,9 @@ struct mf_field {
      *     - "dl_vlan_pcp" is 1 byte but only 3 bits.
      *     - "is_frag" is 1 byte but only 2 bits.
      *     - "ipv6_label" is 4 bytes but only 20 bits.
+     *     - "mpls_label" is 4 bytes but only 20 bits.
+     *     - "mpls_tc"    is 1 byte but only 3 bits.
+     *     - "mpls_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 1e59387..8999bfa 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -547,7 +547,7 @@ nx_put_raw(struct ofpbuf *b, bool oxm, const struct match *match,
     int match_len;
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 17);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 18);
 
     /* Metadata. */
     if (match->wc.masks.in_port) {
@@ -589,6 +589,24 @@ nx_put_raw(struct ofpbuf *b, bool oxm, const struct match *match,
                     match->wc.masks.vlan_tci);
     }
 
+
+    /* MPLS. */
+    if (flow->dl_type == htons(ETH_TYPE_MPLS) ||
+        flow->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
+        if (match->wc.masks.mpls_lse & htonl(MPLS_TC_MASK)) {
+            nxm_put_8(b,OXM_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 5a1d31c..5ffbaef 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -18,6 +18,7 @@
 #include <arpa/inet.h>
 #include "odp-util.h"
 #include <errno.h>
+#include <assert.h>
 #include <inttypes.h>
 #include <math.h>
 #include <netinet/in.h>
@@ -73,13 +74,13 @@ 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_MPLS: return sizeof(ovs_be32);
     case OVS_ACTION_ATTR_SET: return -2;
     case OVS_ACTION_ATTR_SAMPLE: return -2;
 
     case OVS_ACTION_ATTR_UNSPEC:
-    case OVS_ACTION_ATTR_PUSH_MPLS:
-    case OVS_ACTION_ATTR_POP_MPLS:
-    case OVS_ACTION_ATTR_SET_MPLS:
     case __OVS_ACTION_ATTR_MAX:
         return -1;
     }
@@ -109,8 +110,8 @@ ovs_key_attr_to_string(enum ovs_key_attr attr)
     case OVS_KEY_ATTR_ARP: return "arp";
     case OVS_KEY_ATTR_ND: return "nd";
     case OVS_KEY_ATTR_TUN_ID: return "tun_id";
+    case OVS_KEY_ATTR_MPLS: return "mpls";
 
-    case OVS_KEY_ATTR_MPLS:
     case __OVS_KEY_ATTR_MAX:
     default:
         snprintf(unknown_attr, sizeof unknown_attr, "key%u",
@@ -277,6 +278,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;
@@ -315,13 +326,29 @@ 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_SET_MPLS: {
+        ovs_be32 label = nl_attr_get_be32(a);
+        ds_put_cstr(ds, "set_mpls(");
+        format_mpls_lse(ds, label);
+        ds_put_char(ds, ')');
+        break;
+    }
     case OVS_ACTION_ATTR_SAMPLE:
         format_odp_sample_action(ds, a);
         break;
     case OVS_ACTION_ATTR_UNSPEC:
-    case OVS_ACTION_ATTR_PUSH_MPLS:
-    case OVS_ACTION_ATTR_POP_MPLS:
-    case OVS_ACTION_ATTR_SET_MPLS:
     case __OVS_ACTION_ATTR_MAX:
     default:
         format_generic_odp_action(ds, a);
@@ -613,6 +640,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);
@@ -623,7 +651,6 @@ odp_flow_key_attr_len(uint16_t type)
     case OVS_KEY_ATTR_ND: return sizeof(struct ovs_key_nd);
 
     case OVS_KEY_ATTR_UNSPEC:
-    case OVS_KEY_ATTR_MPLS:
     case __OVS_KEY_ATTR_MAX:
         return -1;
     }
@@ -723,6 +750,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)));
@@ -809,7 +844,6 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds)
     }
 
     case OVS_KEY_ATTR_UNSPEC:
-    case OVS_KEY_ATTR_MPLS:
     case __OVS_KEY_ATTR_MAX:
     default:
         format_generic_odp_key(a, ds);
@@ -884,6 +918,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)
@@ -993,6 +1036,22 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
     }
 
     {
+        int label, tc, ttl, bos;
+        int n = -1;
+
+        if (sscanf(s, "mpls(label=%"SCNi32",tc=%i,ttl=%i,bos=%i)%n",
+                    &label, &tc, &ttl, &bos, &n) > 0 &&
+                    n > 0) {
+            struct ovs_key_mpls *mpls;
+
+            mpls = nl_msg_put_unspec_uninit(key, OVS_KEY_ATTR_MPLS,
+                                            sizeof *mpls);
+            mpls->mpls_top_label = mpls_lse_from_components(label, tc, ttl, bos);
+            return n;
+        }
+    }
+
+    {
         ovs_be32 ipv4_src;
         ovs_be32 ipv4_dst;
         int ipv4_proto;
@@ -1336,6 +1395,14 @@ odp_flow_key_from_flow(struct ofpbuf *buf, const struct flow *flow)
         memcpy(arp_key->arp_tha, flow->arp_tha, ETH_ADDR_LEN);
     }
 
+    if (flow->mpls_lse != htonl(0)) {
+        struct ovs_key_mpls *mpls_key;
+
+        mpls_key = nl_msg_put_unspec_uninit(buf, OVS_KEY_ATTR_MPLS,
+                                            sizeof *mpls_key);
+        mpls_key->mpls_top_label = flow->mpls_lse;
+    }
+
     if ((flow->dl_type == htons(ETH_TYPE_IP)
          || flow->dl_type == htons(ETH_TYPE_IPV6))
         && !(flow->nw_frag & FLOW_NW_FRAG_LATER)) {
@@ -1542,13 +1609,80 @@ parse_ethertype(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
 
 static enum odp_key_fitness
 parse_l3_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
+                   uint64_t present_attrs, int out_of_range_attr,
+                   uint64_t expected_attrs, struct flow *flow,
+                   const struct nlattr *key, size_t key_len);
+
+/* Parse MPLS header attributes. */
+static enum odp_key_fitness
+parse_mpls_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
+                   uint64_t present_attrs, int out_of_range_attr,
+                   uint64_t expected_attrs, struct flow *flow,
+                   const struct nlattr *key, size_t key_len)
+{
+    enum odp_key_fitness fitness, encap_fitness;
+    ovs_be32 mpls_lse;
+    ovs_be16 dl_type;
+
+    /* Calculate fitness of outer attributes. */
+    expected_attrs |= (UINT64_C(1) << OVS_KEY_ATTR_MPLS);
+    fitness = check_expectations(present_attrs, out_of_range_attr,
+                                 expected_attrs, key, key_len);
+
+    /* Get the MPLS LSE value. */
+    if (!(present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_MPLS))) {
+        return ODP_FIT_TOO_LITTLE;
+    }
+    mpls_lse = nl_attr_get_be32(attrs[OVS_KEY_ATTR_MPLS]);
+    if (mpls_lse == htonl(0)) {
+        /* Corner case for a truncated MPLS header. */
+        return fitness;
+    }
+
+    /* Set mpls_lse. */
+    flow->mpls_lse = mpls_lse;
+
+
+    fitness = check_expectations(present_attrs, out_of_range_attr,
+                                 expected_attrs, key, key_len);
+
+    /* Try to guess what the encapsulated ethernet type was
+     * in order to try and fill out the flow more fully */
+    dl_type = flow->dl_type;
+    if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_IPV4)) {
+        flow->dl_type = htons(ETH_TYPE_IP);
+    } else if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_IPV6)) {
+        flow->dl_type = htons(ETH_TYPE_IPV6);
+    } else if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_ARP)) {
+        flow->dl_type = htons(ETH_TYPE_ARP);
+    } else {
+        /* Nothing to work with, abandon hope of further processing */
+        return fitness;
+    }
+    encap_fitness = parse_l3_onward(attrs, present_attrs,
+                                    out_of_range_attr, expected_attrs,
+                                    flow, key, key_len);
+    flow->encap_dl_type = flow->dl_type;
+    flow->dl_type = dl_type;
+
+    /* The overall fitness is the worse of the outer and inner attributes. */
+    return MAX(encap_fitness, fitness);
+}
+
+static enum odp_key_fitness
+parse_l3_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
                 uint64_t present_attrs, int out_of_range_attr,
                 uint64_t expected_attrs, struct flow *flow,
                 const struct nlattr *key, size_t key_len)
 {
     static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
 
-    if (flow->dl_type == htons(ETH_TYPE_IP)) {
+    /* Parse MPLS label stack entry */
+    if (flow->dl_type == htons(ETH_TYPE_MPLS) ||
+        flow->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
+        return parse_mpls_onward(attrs, present_attrs, out_of_range_attr,
+                                  expected_attrs, flow, key, key_len);
+    } else if (flow->dl_type == htons(ETH_TYPE_IP)) {
         expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_IPV4;
         if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_IPV4)) {
             const struct ovs_key_ipv4 *ipv4_key;
@@ -1911,6 +2045,33 @@ commit_vlan_action(const struct flow *flow, struct flow *base,
 }
 
 static void
+commit_mpls_action(const struct flow *flow, struct flow *base,
+                   struct ofpbuf *odp_actions)
+{
+    if (flow->mpls_lse == base->mpls_lse) {
+        return;
+    }
+
+    if (base->mpls_lse != htonl(0) && flow->mpls_lse == htonl(0)) {
+        nl_msg_put_be16(odp_actions, OVS_ACTION_ATTR_POP_MPLS,
+                        flow->dl_type);
+    } else if (base->mpls_lse == htonl(0) && flow->mpls_lse != htonl(0)) {
+        struct ovs_action_push_mpls *mpls;
+
+        mpls = nl_msg_put_unspec_uninit(odp_actions, OVS_ACTION_ATTR_PUSH_MPLS,
+                                        sizeof *mpls);
+        memset(mpls, 0, sizeof *mpls);
+        mpls->mpls_ethertype = flow->dl_type;
+        mpls->mpls_label = flow->mpls_lse;
+    } else {
+        nl_msg_put_be32(odp_actions, OVS_ACTION_ATTR_SET_MPLS, flow->mpls_lse);
+    }
+
+    base->dl_type = flow->dl_type;
+    base->mpls_lse = flow->mpls_lse;
+}
+
+static void
 commit_set_ipv4_action(const struct flow *flow, struct flow *base,
                      struct ofpbuf *odp_actions)
 {
@@ -1964,7 +2125,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)
@@ -2037,6 +2197,7 @@ commit_odp_actions(const struct flow *flow, struct flow *base,
     commit_set_tun_id_action(flow, base, odp_actions);
     commit_set_ether_addr_action(flow, base, odp_actions);
     commit_vlan_action(flow, base, odp_actions);
+    commit_mpls_action(flow, base, odp_actions);
     commit_set_nw_action(flow, base, odp_actions);
     commit_set_port_action(flow, base, odp_actions);
     commit_set_priority_action(flow, base, odp_actions);
diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
index 24ee741..0640882 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -378,6 +378,26 @@ ofpact_from_nxast(const union ofp_action *a, enum ofputil_action_code code,
     case OFPUTIL_NXAST_CONTROLLER:
         controller_from_openflow((const struct nx_action_controller *) a, out);
         break;
+
+    case OFPUTIL_NXAST_PUSH_MPLS: {
+        struct nx_action_push *nxap = (struct nx_action_push *)a;
+        if (nxap->ethertype != htons(ETH_TYPE_MPLS) &&
+            nxap->ethertype != htons(ETH_TYPE_MPLS_MCAST)) {
+            return OFPERR_OFPBAC_BAD_ARGUMENT;
+        }
+        ofpact_put_PUSH_MPLS(out)->ethertype = nxap->ethertype;
+        break;
+    }
+
+    case OFPUTIL_NXAST_POP_MPLS: {
+        struct nx_action_pop_mpls *nxapm = (struct nx_action_pop_mpls *)a;
+        if (nxapm->ethertype == htons(ETH_TYPE_MPLS) ||
+            nxapm->ethertype == htons(ETH_TYPE_MPLS_MCAST)) {
+            return OFPERR_OFPBAC_BAD_ARGUMENT;
+        }
+        ofpact_put_POP_MPLS(out)->ethertype = nxapm->ethertype;
+        break;
+    }
     }
 
     return error;
@@ -722,6 +742,26 @@ 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 (oap->ethertype != htons(ETH_TYPE_MPLS) &&
+            oap->ethertype != htons(ETH_TYPE_MPLS_MCAST)) {
+            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 (oapm->ethertype == htons(ETH_TYPE_MPLS) ||
+            oapm->ethertype == htons(ETH_TYPE_MPLS_MCAST)) {
+            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);
@@ -1038,6 +1078,8 @@ ofpact_check__(const struct ofpact *a, int max_ports)
 
     case OFPACT_NOTE:
     case OFPACT_EXIT:
+    case OFPACT_PUSH_MPLS:
+    case OFPACT_POP_MPLS:
         return 0;
 
     default:
@@ -1242,6 +1284,16 @@ ofpact_to_nxast(const struct ofpact *a, struct ofpbuf *out)
         ofputil_put_NXAST_EXIT(out);
         break;
 
+    case OFPACT_PUSH_MPLS:
+        ofputil_put_NXAST_PUSH_MPLS(out)->ethertype =
+            ofpact_get_PUSH_MPLS(a)->ethertype;
+        break;
+
+    case OFPACT_POP_MPLS:
+        ofputil_put_NXAST_POP_MPLS(out)->ethertype =
+            ofpact_get_POP_MPLS(a)->ethertype;
+        break;
+
     case OFPACT_OUTPUT:
     case OFPACT_ENQUEUE:
     case OFPACT_SET_VLAN_VID:
@@ -1359,6 +1411,8 @@ ofpact_to_openflow10(const struct ofpact *a, struct ofpbuf *out)
     case OFPACT_AUTOPATH:
     case OFPACT_NOTE:
     case OFPACT_EXIT:
+    case OFPACT_PUSH_MPLS:
+    case OFPACT_POP_MPLS:
         ofpact_to_nxast(a, out);
         break;
     }
@@ -1451,6 +1505,16 @@ ofpact_to_openflow11(const struct ofpact *a, struct ofpbuf *out)
             = htons(ofpact_get_SET_L4_DST_PORT(a)->port);
         break;
 
+    case OFPACT_PUSH_MPLS:
+        ofputil_put_OFPAT11_PUSH_MPLS(out)->ethertype =
+            ofpact_get_PUSH_MPLS(a)->ethertype;
+        break;
+
+    case OFPACT_POP_MPLS:
+        ofputil_put_OFPAT11_POP_MPLS(out)->ethertype =
+            ofpact_get_POP_MPLS(a)->ethertype;
+        break;
+
     case OFPACT_CONTROLLER:
     case OFPACT_OUTPUT_REG:
     case OFPACT_BUNDLE:
@@ -1548,6 +1612,9 @@ ofpact_outputs_to_port(const struct ofpact *ofpact, uint16_t port)
     case OFPACT_AUTOPATH:
     case OFPACT_NOTE:
     case OFPACT_EXIT:
+    case OFPACT_PUSH_MPLS:
+    case OFPACT_POP_MPLS:
+
     default:
         return false;
     }
@@ -1811,6 +1878,16 @@ ofpact_format(const struct ofpact *a, struct ds *s)
         print_note(ofpact_get_NOTE(a), s);
         break;
 
+    case OFPACT_PUSH_MPLS:
+        ds_put_format(s, "push_mpls:0x%04"PRIx16,
+                      ntohs(ofpact_get_PUSH_MPLS(a)->ethertype));
+        break;
+
+    case OFPACT_POP_MPLS:
+        ds_put_format(s, "pop_mpls:0x%04"PRIx16,
+                      ntohs(ofpact_get_POP_MPLS(a)->ethertype));
+        break;
+
     case OFPACT_EXIT:
         ds_put_cstr(s, "exit");
         break;
diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h
index b68de67..343ce13 100644
--- a/lib/ofp-actions.h
+++ b/lib/ofp-actions.h
@@ -70,6 +70,8 @@
     DEFINE_OFPACT(REG_MOVE,        ofpact_reg_move,      ofpact)    \
     DEFINE_OFPACT(REG_LOAD,        ofpact_reg_load,      ofpact)    \
     DEFINE_OFPACT(DEC_TTL,         ofpact_cnt_ids,       cnt_ids)   \
+    DEFINE_OFPACT(PUSH_MPLS,       ofpact_push,          ofpact)    \
+    DEFINE_OFPACT(POP_MPLS,        ofpact_pop_mpls,      ofpact)    \
                                                                     \
     /* Metadata. */                                                 \
     DEFINE_OFPACT(SET_TUNNEL,      ofpact_tunnel,        ofpact)    \
@@ -302,6 +304,22 @@ struct ofpact_reg_load {
     union mf_subvalue subvalue; /* Least-significant bits are used. */
 };
 
+/* OFPACT_PUSH_VLAN/MPLS/PBB
+ *
+ * used for NXAST_PUSH_MPLS, OFPAT13_PUSH_VLAN/MPLS/PBB */
+struct ofpact_push {
+    struct ofpact ofpact;
+    ovs_be16 ethertype;
+};
+
+/* OFPACT_POP_MPLS
+ *
+ * used for NXAST_POP_MPLS */
+struct ofpact_pop_mpls {
+    struct ofpact ofpact;
+    ovs_be16 ethertype;
+};
+
 /* OFPACT_SET_TUNNEL.
  *
  * Used for NXAST_SET_TUNNEL, NXAST_SET_TUNNEL64. */
diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
index 2d2daa4..78048e8 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -517,6 +517,18 @@ parse_named_action(enum ofputil_action_code code, const struct flow *flow,
     case OFPUTIL_NXAST_CONTROLLER:
         parse_controller(ofpacts, arg);
         break;
+
+    case OFPUTIL_OFPAT11_PUSH_MPLS:
+    case OFPUTIL_NXAST_PUSH_MPLS:
+        ofpact_put_PUSH_MPLS(ofpacts)->ethertype =
+            htons(str_to_u16(arg, "push_mpls"));
+        break;
+
+    case OFPUTIL_OFPAT11_POP_MPLS:
+    case OFPUTIL_NXAST_POP_MPLS:
+        ofpact_put_POP_MPLS(ofpacts)->ethertype =
+            htons(str_to_u16(arg, "pop_mpls"));
+        break;
     }
 }
 
@@ -574,6 +586,8 @@ parse_protocol(const char *name, const struct protocol **p_out)
         { "icmp6", ETH_TYPE_IPV6, IPPROTO_ICMPV6 },
         { "tcp6", ETH_TYPE_IPV6, IPPROTO_TCP },
         { "udp6", ETH_TYPE_IPV6, IPPROTO_UDP },
+        { "mpls", ETH_TYPE_MPLS, 0 },
+        { "mplsm", ETH_TYPE_MPLS_MCAST, 0 },
     };
     const struct protocol *p;
 
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index 707699e..105fd59 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -617,6 +617,10 @@ ofp10_match_to_string(const struct ofp10_match *om, int verbosity)
             }
         } else if (om->dl_type == htons(ETH_TYPE_ARP)) {
             ds_put_cstr(&f, "arp,");
+        } else if (om->dl_type == htons(ETH_TYPE_MPLS)) {
+            ds_put_cstr(&f, "mpls,");
+        } else if (om->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
+            ds_put_cstr(&f, "mplsm,");
         } else {
             skip_type = false;
         }
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index 00dfea3..bb9f5e9 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -84,7 +84,7 @@ ofputil_netmask_to_wcbits(ovs_be32 netmask)
 void
 ofputil_wildcard_from_ofpfw10(uint32_t ofpfw, struct flow_wildcards *wc)
 {
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 17);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 18);
 
     /* Initialize most of wc. */
     flow_wildcards_init_catchall(wc);
@@ -900,7 +900,7 @@ ofputil_usable_protocols(const struct match *match)
 {
     const struct flow_wildcards *wc = &match->wc;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 17);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 18);
 
     /* NXM and OF1.1+ supports bitwise matching on ethernet addresses. */
     if (!eth_mask_is_exact(wc->masks.dl_src)
@@ -969,6 +969,21 @@ ofputil_usable_protocols(const struct match *match)
         return OFPUTIL_P_NXM_ANY;
     }
 
+    /* NXM and OF1.3 support matching MPLS label */
+    if (wc->masks.mpls_lse & htonl(MPLS_LABEL_MASK)) {
+        return OFPUTIL_P_NXM_ANY;
+    }
+
+    /* NXM and OF1.1+ support matching MPLS TC */
+    if (wc->masks.mpls_lse & htonl(MPLS_TC_MASK)) {
+        return OFPUTIL_P_NXM_ANY;
+    }
+
+    /* NXM and OF1.1+ support matching MPLS stack flag */
+    if (wc->masks.mpls_lse & htonl(MPLS_BOS_MASK)) {
+        return OFPUTIL_P_NXM_ANY;
+    }
+
     /* Other formats can express this rule. */
     return OFPUTIL_P_ANY;
 }
@@ -3756,7 +3771,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;
@@ -3784,6 +3800,9 @@ ofputil_normalize_match__(struct match *match, bool may_log)
         }
     } else if (match->flow.dl_type == htons(ETH_TYPE_ARP)) {
         may_match = MAY_NW_PROTO | MAY_NW_ADDR | MAY_ARP_SHA | MAY_ARP_THA;
+    } else if (match->flow.dl_type == htons(ETH_TYPE_MPLS) ||
+               match->flow.dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
+        may_match = MAY_MPLS;
     } else {
         may_match = 0;
     }
@@ -3816,6 +3835,9 @@ ofputil_normalize_match__(struct match *match, bool may_log)
     if (!(may_match & MAY_ND_TARGET)) {
         wc.masks.nd_target = in6addr_any;
     }
+    if (!(may_match & MAY_MPLS)) {
+        wc.masks.mpls_lse = htonl(0);
+    }
 
     /* Log any changes. */
     if (!flow_wildcards_equal(&wc, &match->wc)) {
diff --git a/lib/ofp-util.def b/lib/ofp-util.def
index 4d451b0..2fc22d1 100644
--- a/lib/ofp-util.def
+++ b/lib/ofp-util.def
@@ -32,6 +32,8 @@ OFPAT11_ACTION(OFPAT11_SET_TP_SRC,   ofp_action_tp_port,  0, "mod_tp_src")
 OFPAT11_ACTION(OFPAT11_SET_TP_DST,   ofp_action_tp_port,  0, "mod_tp_dst")
 //OFPAT11_ACTION(OFPAT11_PUSH_VLAN,    ofp11_action_push,   0, "push_vlan")
 //OFPAT11_ACTION(OFPAT11_POP_VLAN,     ofp_action_header,   0, "pop_vlan")
+OFPAT11_ACTION(OFPAT11_PUSH_MPLS,    ofp11_action_push,   0, "push_mpls")
+OFPAT11_ACTION(OFPAT11_POP_MPLS,     ofp11_action_pop_mpls, 0, "pop_mpls")
 //OFPAT11_ACTION(OFPAT11_SET_QUEUE,    ofp11_action_set_queue, 0, "set_queue")
 //OFPAT11_ACTION(OFPAT11_SET_NW_TTL,   ofp11_action_nw_ttl, 0, "set_nw_ttl")
 //OFPAT11_ACTION(OFPAT11_DEC_NW_TTL,   ofp_action_header,   0, "dec_ttl")
@@ -60,6 +62,8 @@ NXAST_ACTION(NXAST_DEC_TTL,         nx_action_header,       0, "dec_ttl")
 NXAST_ACTION(NXAST_FIN_TIMEOUT,     nx_action_fin_timeout,  0, "fin_timeout")
 NXAST_ACTION(NXAST_CONTROLLER,      nx_action_controller,   0, "controller")
 NXAST_ACTION(NXAST_DEC_TTL_CNT_IDS, nx_action_cnt_ids,      1, NULL)
+NXAST_ACTION(NXAST_PUSH_MPLS,       nx_action_push,         0, "push_mpls")
+NXAST_ACTION(NXAST_POP_MPLS,        nx_action_pop_mpls,     0, "pop_mpls")
 
 #undef OFPAT10_ACTION
 #undef OFPAT11_ACTION
diff --git a/lib/ofpbuf.c b/lib/ofpbuf.c
index a7d4c73..9f5e908 100644
--- a/lib/ofpbuf.c
+++ b/lib/ofpbuf.c
@@ -30,7 +30,7 @@ ofpbuf_use__(struct ofpbuf *b, void *base, size_t allocated,
     b->allocated = allocated;
     b->source = source;
     b->size = 0;
-    b->l2 = b->l3 = b->l4 = b->l7 = NULL;
+    b->l2 = b->l2_5 = b->l3 = b->l4 = b->l7 = NULL;
     list_poison(&b->list_node);
     b->private_p = NULL;
 }
@@ -177,6 +177,9 @@ ofpbuf_clone_with_headroom(const struct ofpbuf *buffer, size_t headroom)
     if (buffer->l2) {
         new_buffer->l2 = (char *) buffer->l2 + data_delta;
     }
+    if (buffer->l2_5) {
+        new_buffer->l2_5 = (char *) buffer->l2_5 + data_delta;
+    }
     if (buffer->l3) {
         new_buffer->l3 = (char *) buffer->l3 + data_delta;
     }
@@ -296,6 +299,9 @@ ofpbuf_resize__(struct ofpbuf *b, size_t new_headroom, size_t new_tailroom)
         if (b->l2) {
             b->l2 = (char *) b->l2 + data_delta;
         }
+        if (b->l2_5) {
+            b->l2_5 = (char *) b->l2_5 + data_delta;
+        }
         if (b->l3) {
             b->l3 = (char *) b->l3 + data_delta;
         }
diff --git a/lib/ofpbuf.h b/lib/ofpbuf.h
index 520455d..bae3c0a 100644
--- a/lib/ofpbuf.h
+++ b/lib/ofpbuf.h
@@ -43,6 +43,7 @@ struct ofpbuf {
     size_t size;                /* Number of bytes in use. */
 
     void *l2;                   /* Link-level header. */
+    void *l2_5;                 /* MPLS label stack */
     void *l3;                   /* Network-level header. */
     void *l4;                   /* Transport-level header. */
     void *l7;                   /* Application data. */
diff --git a/lib/packets.c b/lib/packets.c
index 16f4fe6..0b85cf9 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -20,6 +20,7 @@
 #include <arpa/inet.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
+#include <netinet/ip6.h>
 #include <stdlib.h>
 #include "byte-order.h"
 #include "csum.h"
@@ -211,6 +212,361 @@ eth_pop_vlan(struct ofpbuf *packet)
     }
 }
 
+/* Set ethertype of the packet. */
+void
+set_ethertype(struct ofpbuf *packet, ovs_be16 eth_type)
+{
+    struct eth_header *eh = packet->data;
+
+    if (eh->eth_type == htons(ETH_TYPE_VLAN)) {
+        /* ethtype for VLAN packets is at L3_offset - 2 bytes. */
+        ovs_be16 *next_ethtype;
+        next_ethtype = (ovs_be16 *)((char *)packet->l3 - 2);
+        *next_ethtype = eth_type;
+    } else {
+        eh->eth_type = eth_type;
+    }
+}
+
+/* Get ethertype of the packet. */
+static ovs_be16
+get_ethertype(struct ofpbuf *packet)
+{
+    struct eth_header *eh = packet->data;
+    char *mh = packet->l2_5;
+    ovs_be16 *ethtype = NULL;
+
+    if (eh->eth_type == htons(ETH_TYPE_VLAN)) {
+        if (mh != NULL) {
+            ethtype = (ovs_be16 *)(mh - 2);
+        } else {
+            ethtype = (ovs_be16 *)((char *)packet->l3 - 2);
+        }
+        return *ethtype;
+    } else {
+        return eh->eth_type;
+    }
+}
+
+/* Extract ttl and tos from ipv4 or ipv6 header
+   for non-IP pick default value. */
+static int
+get_label_ttl_and_tc(struct ofpbuf* packet, uint8_t *ttl,
+                     uint8_t *tc, uint8_t *label)
+{
+    struct eth_header *eh = packet->data;
+
+    if (packet->size < sizeof *eh) {
+        return -1;
+    }
+
+    switch (ntohs(get_ethertype(packet))) {
+    case ETH_TYPE_IP: {
+        struct ip_header *ih = packet->l3;
+
+        if (packet->size < sizeof *eh + sizeof *ih) {
+            return -1;
+        }
+        *ttl = ih->ip_ttl;
+        *tc = 0; /* As per OpenFlow 1.1 Spec. */
+        *label = 0; /* IPV4 Explicit null label. */
+        break;
+    }
+
+    case ETH_TYPE_IPV6: {
+        struct ip6_hdr   *ih6 = packet->l3;
+
+        if (packet->size < sizeof *eh + sizeof *ih6) {
+            return -1;
+        }
+        *ttl = ih6->ip6_hlim;
+        *tc = 0; /* As per OpenFlow 1.1 Spec. */
+        *label = 2; /* IPV6 Explicit null label. */
+        break;
+    }
+
+    case ETH_TYPE_MPLS:
+    case ETH_TYPE_MPLS_MCAST: {
+        struct mpls_hdr *mh = packet->l2_5;
+
+        if (packet->size < sizeof *eh + sizeof *mh) {
+            return -1;
+        }
+        *ttl = mpls_lse_to_ttl(mh->mpls_lse);
+        *tc = mpls_lse_to_tc(mh->mpls_lse);
+        *label = mpls_lse_to_label(mh->mpls_lse);
+        break;
+    }
+
+    default:
+        return -1;
+    }
+
+    return 0;
+}
+
+/* Set MPLS tag time-to-live. */
+static void
+set_mpls_lse_ttl(ovs_be32 *tag, ovs_be32 ttl)
+{
+    *tag &= ~htonl(MPLS_TTL_MASK);
+    *tag |= ttl & htonl(MPLS_TTL_MASK);
+}
+
+/* Set MPLS tag traffic-class. */
+static void
+set_mpls_lse_tc(ovs_be32 *tag, ovs_be32 tc)
+{
+    *tag &= ~htonl(MPLS_TC_MASK);
+    *tag |= tc & htonl(MPLS_TC_MASK);
+}
+
+/* Set MPLS tag label. */
+static void
+set_mpls_lse_label(ovs_be32 *tag, ovs_be32 label)
+{
+    *tag &= ~htonl(MPLS_LABEL_MASK);
+    *tag |= label & htonl(MPLS_LABEL_MASK);
+}
+
+/* Set MPLS tag stack. */
+static void
+set_mpls_lse_stack(ovs_be32 *tag, ovs_be32 stack)
+{
+    *tag &= ~htonl(MPLS_BOS_MASK);
+    *tag |= stack & htonl(MPLS_BOS_MASK);
+}
+
+/* Set MPLS lse from actions. */
+static void
+set_new_mpls_lse(struct mpls_hdr *mh, ovs_be32 mpls_lse)
+{
+    mh->mpls_lse = mpls_lse;
+}
+
+/* Set MPLS label, MPLS TC, MPLS ttl and MPLS stack. */
+static void
+set_mpls_lse_values(ovs_be32 *tag, uint8_t ttl, uint8_t stack,
+                    uint8_t tc, uint32_t label)
+{
+    set_mpls_lse_ttl(tag, htonl(ttl << MPLS_TTL_SHIFT));
+    set_mpls_lse_tc(tag, htonl(tc << MPLS_TC_SHIFT));
+    set_mpls_lse_label(tag, htonl(label << MPLS_LABEL_SHIFT));
+    set_mpls_lse_stack(tag, htonl(stack << MPLS_BOS_SHIFT));
+}
+
+/* Adjust L2 and L2.5 data after pushing new mpls shim header. */
+static void
+push_mpls_lse(struct ofpbuf *packet, struct mpls_hdr *mh)
+{
+    char * header;
+    size_t len;
+    header = ofpbuf_push_uninit(packet, MPLS_HLEN);
+    len = (char *)packet->l2_5 - (char *)packet->l2;
+    memmove(header, packet->l2, len);
+    memcpy(header + len, mh, sizeof *mh);
+    packet->l2 = (char*)packet->l2 - MPLS_HLEN;
+    packet->l2_5 = (char*)packet->l2_5 - MPLS_HLEN;
+}
+
+/* Decrement MPLS TTL from the packet.
+ * 'packet->l2_5' must initially point to 'packet''s MPLS Label stack. */
+void
+dec_mpls_ttl(struct ofpbuf *packet, uint8_t new_ttl)
+{
+    ovs_be16 eth_type = htons(0);
+    struct eth_header *eh = packet->data;
+    struct mpls_hdr *mh = packet->l2_5;
+
+    if (packet->size < sizeof *eh) {
+        return;
+    }
+
+    /* Packet type should be mpls to decrement ttl. */
+    eth_type = get_ethertype(packet);
+
+    if (eth_type == htons(ETH_TYPE_MPLS) ||
+        eth_type == htons(ETH_TYPE_MPLS_MCAST)) {
+
+        /* Update decremented ttl into mpls header. */
+        set_mpls_lse_ttl(&mh->mpls_lse, htonl(new_ttl << MPLS_TTL_SHIFT));
+    }
+}
+
+/* Copy MPLS TTL from the packet either ipv4/ipv6.
+ * 'packet->l2_5' must initially point to 'packet''s MPLS Label stack. */
+void
+copy_mpls_ttl_in(struct ofpbuf *packet, uint8_t new_ttl)
+{
+    struct eth_header *eh = packet->data;
+    struct mpls_hdr *mh = packet->l2_5;
+    struct ip_header *ih = packet->l3;
+    struct ip6_hdr *ih6 = packet->l3;
+    ovs_be16 eth_type = htons(0);
+    size_t hdr_size = sizeof *eh + sizeof *mh + sizeof *ih;
+
+    if (packet->size < hdr_size) {
+        return;
+    }
+
+    /* Packet type should be mpls to copy ttl to l3. */
+    eth_type = get_ethertype(packet);
+    if (eth_type == htons(ETH_TYPE_MPLS) ||
+        eth_type == htons(ETH_TYPE_MPLS_MCAST)) {
+
+        /* If bottom of the stack handle IP checksum. */
+        if (mh->mpls_lse & htonl(MPLS_BOS_MASK)) {
+            if (IP_VER(ih->ip_ihl_ver) == IP_VERSION) {
+                /* Change the ip checksum. */
+                uint8_t *field = &ih->ip_ttl;
+                ih->ip_csum = recalc_csum16(ih->ip_csum,
+                                 htons(*field << 8), htons(new_ttl << 8));
+                ih->ip_ttl = new_ttl;
+            } else if (IP6_VER(ih6->ip6_vfc) == IP6_VERSION) {
+                ih6->ip6_hlim = new_ttl;
+            }
+        } else {
+            struct mpls_hdr *mh2;
+            mh2 = (struct mpls_hdr *)((char *) packet->l2_5 + sizeof *mh);
+            set_mpls_lse_ttl(&mh2->mpls_lse, htonl(new_ttl << MPLS_TTL_SHIFT));
+        }
+    }
+}
+
+/* Copy MPLS TTL to the packet layer3 only ipv4/ipv6.
+ * 'packet->l2_5' must initially point to 'packet''s MPLS Label stack. */
+void
+copy_mpls_ttl_out(struct ofpbuf *packet, uint8_t new_ttl)
+{
+    struct eth_header *eh = packet->data;
+    struct mpls_hdr *mh = packet->l2_5;
+    struct ip_header *ih = packet->l3;
+    struct ip6_hdr   *ih6 = packet->l3;
+    ovs_be16 eth_type = htons(0);
+    size_t hdr_size = sizeof *eh + sizeof *mh + sizeof *ih;
+
+    /* TTL sent from ofproto-dpif.c is not the correct one,
+     * hence ignore it. */
+    if (packet->size < hdr_size) {
+        return;
+    }
+
+    /* Packet type should me mpls to copy ttl from l3. */
+    eth_type = get_ethertype(packet);
+    if (eth_type == htons(ETH_TYPE_MPLS) ||
+        eth_type == htons(ETH_TYPE_MPLS_MCAST)) {
+
+        /* If bottom of the stack copy from l3. */
+        if (mh->mpls_lse & htonl(MPLS_BOS_MASK)) {
+            uint8_t nh_ttl;
+            /* Get ipv4 or ipv6 or default ttl. */
+            if (IP_VER(ih->ip_ihl_ver) == IP_VERSION) {
+                nh_ttl = ih->ip_ttl;
+            } else if (IP6_VER(ih6->ip6_vfc) == IP6_VERSION) {
+                nh_ttl = ih6->ip6_hlim;
+            } else {
+                nh_ttl = 64; /* Default ttl for non-IP. */
+            }
+            set_mpls_lse_ttl(&mh->mpls_lse, htonl(nh_ttl << MPLS_TTL_SHIFT));
+        } else {
+            struct mpls_hdr *mh2;
+            mh2 = (struct mpls_hdr *)((char *) packet->l2_5 + sizeof *mh);
+            new_ttl = mpls_lse_to_ttl(mh2->mpls_lse);
+            set_mpls_lse_ttl(&mh->mpls_lse, htonl(new_ttl << MPLS_TTL_SHIFT));
+        }
+    }
+}
+
+/* Set MPLS label stack entry to outermost MPLS header.*/
+void
+set_mpls_lse(struct ofpbuf *packet, ovs_be32 mpls_lse)
+{
+    struct eth_header *eh = packet->data;
+    struct mpls_hdr *mh = packet->l2_5;
+    ovs_be16 eth_type = htons(0);
+
+    if (packet->size < sizeof *eh) {
+        return;
+    }
+
+    /* Packet type should me mpls to set label stack entry. */
+    eth_type = get_ethertype(packet);
+    if (eth_type == htons(ETH_TYPE_MPLS) ||
+        eth_type == htons(ETH_TYPE_MPLS_MCAST)) {
+        /* Update mpls label stack entry. */
+        set_new_mpls_lse(mh, mpls_lse);
+    }
+}
+
+/* Push MPLS label stack entry onto packet. */
+void
+push_mpls(struct ofpbuf *packet, ovs_be16 ethtype)
+{
+    struct eth_header *eh = packet->data;
+    uint8_t nh_ttl, mpls_tc, label;
+    ovs_be16 eth_type = htons(0);
+    struct mpls_hdr mh;
+
+    if (packet->size < sizeof *eh ||
+        (ethtype != htons(ETH_TYPE_MPLS) &&
+         ethtype != htons(ETH_TYPE_MPLS_MCAST))) {
+        return;
+    }
+    /* Get Label, time-to-live and tc from L3 or L2.5. */
+    if (get_label_ttl_and_tc(packet, &nh_ttl, &mpls_tc, &label)) {
+        return;
+    }
+
+    /* Get the packet ether_type. */
+    eth_type = get_ethertype(packet);
+
+    if (eth_type == htons(ETH_TYPE_MPLS) ||
+        eth_type == htons(ETH_TYPE_MPLS_MCAST)) {
+        set_mpls_lse_values(&mh.mpls_lse, nh_ttl, 0, mpls_tc, label);
+    } else {
+        /* Set ethtype and mpls label stack entry. */
+        set_ethertype(packet, ethtype);
+        set_mpls_lse_values(&mh.mpls_lse, nh_ttl, 1, mpls_tc, label);
+        packet->l2_5 = packet->l3;
+    }
+    /* Push new MPLS shim header onto packet. */
+    push_mpls_lse(packet, &mh);
+}
+
+/* Pop outermost MPLS label stack entry from packet. */
+void
+pop_mpls(struct ofpbuf *packet, ovs_be16 ethtype)
+{
+    struct eth_header *eh = packet->data;
+    struct mpls_hdr *mh = NULL;
+    ovs_be16 eth_type = htons(0);
+
+    if (packet->size < sizeof *eh + sizeof *mh)
+        return;
+
+    eth_type = get_ethertype(packet);
+
+    if (eth_type == htons(ETH_TYPE_MPLS) ||
+        eth_type == htons(ETH_TYPE_MPLS_MCAST)) {
+        size_t len;
+        mh = packet->l2_5;
+        len = (char*)packet->l2_5 - (char*)packet->l2;
+        /* If bottom of the stack set ethertype. */
+        if (mh->mpls_lse & htonl(MPLS_BOS_MASK)) {
+            packet->l3 = packet->l2_5;
+            packet->l2_5 = NULL;
+            set_ethertype(packet, ethtype);
+        } else {
+            packet->l2_5 = (char*)packet->l2_5 + MPLS_HLEN;
+        }
+        /* Shift the l2 header forward. */
+        memmove((char*)packet->data + MPLS_HLEN, packet->data, len);
+        packet->size -= MPLS_HLEN;
+        packet->data = (char*)packet->data + MPLS_HLEN;
+        packet->l2 = (char*)packet->l2 + MPLS_HLEN;
+    }
+}
+
 /* Converts hex digits in 'hex' to an Ethernet packet in '*packetp'.  The
  * caller must free '*packetp'.  On success, returns NULL.  On failure, returns
  * an error message and stores NULL in '*packetp'. */
diff --git a/lib/packets.h b/lib/packets.h
index 24b51da..299da8f 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -140,6 +140,8 @@ void compose_rarp(struct ofpbuf *, const uint8_t eth_src[ETH_ADDR_LEN]);
 void eth_push_vlan(struct ofpbuf *, ovs_be16 tci);
 void eth_pop_vlan(struct ofpbuf *);
 
+void set_ethertype(struct ofpbuf *packet, ovs_be16 eth_type);
+
 const char *eth_from_hex(const char *hex, struct ofpbuf **packetp);
 void eth_format_masked(const uint8_t eth[ETH_ADDR_LEN],
                        const uint8_t mask[ETH_ADDR_LEN], struct ds *s);
@@ -147,6 +149,15 @@ void eth_addr_bitand(const uint8_t src[ETH_ADDR_LEN],
                      const uint8_t mask[ETH_ADDR_LEN],
                      uint8_t dst[ETH_ADDR_LEN]);
 
+void set_mpls_ttl(struct ofpbuf *, uint8_t ttl);
+void dec_mpls_ttl(struct ofpbuf *, uint8_t new_ttl);
+void copy_mpls_ttl_in(struct ofpbuf *, uint8_t new_ttl);
+void copy_mpls_ttl_out(struct ofpbuf *, uint8_t new_ttl);
+void set_mpls_tc(struct ofpbuf *, uint8_t tc);
+void set_mpls_lse(struct ofpbuf *, ovs_be32 label);
+void push_mpls(struct ofpbuf *packet, ovs_be16 ethtype);
+void pop_mpls(struct ofpbuf *, ovs_be16 ethtype);
+
 /* Example:
  *
  * uint8_t mac[ETH_ADDR_LEN];
@@ -270,6 +281,76 @@ struct vlan_eth_header {
 } __attribute__((packed));
 BUILD_ASSERT_DECL(VLAN_ETH_HEADER_LEN == sizeof(struct vlan_eth_header));
 
+/* MPLS related definitions */
+#define MPLS_TTL_MASK       0x000000ff
+#define MPLS_TTL_SHIFT      0
+
+#define MPLS_BOS_MASK       0x00000100
+#define MPLS_BOS_SHIFT      8
+
+#define MPLS_TC_MASK        0x00000e00
+#define MPLS_TC_SHIFT       9
+
+#define MPLS_LABEL_MASK     0xfffff000
+#define MPLS_LABEL_SHIFT    12
+
+#define MPLS_HLEN           4
+
+struct mpls_hdr {
+    ovs_be32 mpls_lse;
+};
+BUILD_ASSERT_DECL(MPLS_HLEN == sizeof(struct mpls_hdr));
+
+#define MPLS_ETH_HEADER_LEN (ETH_HEADER_LEN + MPLS_HLEN)
+struct mpls_eth_header {
+    uint8_t  eth_dst[ETH_ADDR_LEN];
+    uint8_t  eth_src[ETH_ADDR_LEN];
+    ovs_be16 eth_type;         /* htons(ETH_TYPE_MPLS) or
+                                  htons(ETH_TYPE_MPLS_MCAST). */
+    ovs_be32 mpls_lse;
+} __attribute__((packed));
+BUILD_ASSERT_DECL(MPLS_ETH_HEADER_LEN == sizeof(struct mpls_eth_header));
+
+/* Given a mpls label stack entry in network byte order
+ * return mpls label */
+static inline uint32_t
+mpls_lse_to_label(ovs_be32 mpls_lse)
+{
+    return (ntohl(mpls_lse) & MPLS_LABEL_MASK) >> MPLS_LABEL_SHIFT;
+}
+
+/* Given a mpls label stack entry in network byte order
+ * return mpls tc */
+static inline int
+mpls_lse_to_tc(ovs_be32 mpls_lse)
+{
+    return (ntohl(mpls_lse) & MPLS_TC_MASK) >> MPLS_TC_SHIFT;
+}
+
+/* Given a mpls label stack entry in network byte order
+ * return mpls ttl */
+static inline int
+mpls_lse_to_ttl(ovs_be32 mpls_lse)
+{
+    return (ntohl(mpls_lse) & MPLS_TTL_MASK) >> MPLS_TTL_SHIFT;
+}
+
+/* Set TTL in mpls lse. */
+static inline void
+flow_set_mpls_lse_ttl(ovs_be32 *mpls_lse, uint8_t ttl)
+{
+    *mpls_lse &= ~htonl(MPLS_TTL_MASK);
+    *mpls_lse |= htonl(ttl << MPLS_TTL_SHIFT);
+}
+
+/* Given a mpls label stack entry in network byte order
+ * return mpls stack */
+static inline int
+mpls_lse_to_bos(ovs_be32 mpls_lse)
+{
+    return (mpls_lse & htonl(MPLS_BOS_MASK)) != 0;
+}
+
 /* The "(void) (ip)[0]" below has no effect on the value, since it's the first
  * argument of a comma expression, but it makes sure that 'ip' is a pointer.
  * This is useful since a common mistake is to pass an integer instead of a
@@ -334,6 +415,8 @@ void ip_format_masked(ovs_be32 ip, ovs_be32 mask, struct ds *);
 
 #define IP_VERSION 4
 
+#define IP_DSCP(ip_tos) ((ip_tos & IP_DSCP_MASK) >> 2)
+
 #define IP_DONT_FRAGMENT  0x4000 /* Don't fragment. */
 #define IP_MORE_FRAGMENTS 0x2000 /* More fragments. */
 #define IP_FRAG_OFF_MASK  0x1fff /* Fragment offset. */
@@ -434,6 +517,11 @@ BUILD_ASSERT_DECL(ARP_ETH_HEADER_LEN == sizeof(struct arp_eth_header));
 /* The IPv6 flow label is in the lower 20 bits of the first 32-bit word. */
 #define IPV6_LABEL_MASK 0x000fffff
 
+#define IP6_VERSION    6
+
+#define IP6_VER(ip6_vfc) ((ip6_vfc) >> 4)
+#define IP6_TC(ip6_flow) ((ip6_flow >> 20) & 0xff)
+
 /* Example:
  *
  * char *string = "1 ::1 2";
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 6e50885..e5aa4e8 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -3934,6 +3934,7 @@ subfacet_should_install(struct subfacet *subfacet, enum slow_path_reason slow,
                         const struct ofpbuf *want_actions)
 {
     enum subfacet_path want_path = subfacet_want_path(slow);
+
     return (want_path != subfacet->path
             || (want_path == SF_FAST_PATH
                 && (subfacet->actions_len != want_actions->size
@@ -4952,6 +4953,7 @@ compose_output_action__(struct action_xlate_ctx *ctx, uint16_t ofp_port,
     const struct ofport_dpif *ofport = get_ofp_port(ctx->ofproto, ofp_port);
     uint16_t odp_port = ofp_port_to_odp_port(ofp_port);
     ovs_be16 flow_vlan_tci = ctx->flow.vlan_tci;
+    ovs_be32 flow_mpls_lse = ctx->flow.mpls_lse;
     uint8_t flow_nw_tos = ctx->flow.nw_tos;
     uint16_t out_port;
 
@@ -4989,6 +4991,7 @@ compose_output_action__(struct action_xlate_ctx *ctx, uint16_t ofp_port,
     ctx->sflow_n_outputs++;
     ctx->nf_output_iface = ofp_port;
     ctx->flow.vlan_tci = flow_vlan_tci;
+    ctx->flow.mpls_lse = flow_mpls_lse;
     ctx->flow.nw_tos = flow_nw_tos;
 }
 
@@ -5136,9 +5139,20 @@ execute_controller_action(struct action_xlate_ctx *ctx, int len,
 
         /* If the Ethernet type is less than ETH_TYPE_MIN, it's likely an 802.2
          * LLC frame.  Calculating the Ethernet type of these frames is more
-         * trouble than seems appropriate for a simple assertion. */
-        assert(ntohs(eh->eth_type) < ETH_TYPE_MIN
-               || eh->eth_type == ctx->flow.dl_type);
+         * trouble than seems appropriate for a simple assertion.
+         *
+         * There is a gratuitous exception for MPLS intended to handle
+         * the cases where an MPLS push or pull action has occurred and
+         * ctx->flow.dl_type will has been updated and the original
+         * value lost. This could be handled more cleanly, for instance by
+         * saving the original value of ctx->flow.dl_type when pushing the
+         * first MPLS label, but that seems somewhat excessive */
+        assert(ntohs(eh->eth_type) < ETH_TYPE_MIN ||
+               eh->eth_type == ctx->flow.dl_type ||
+               ctx->flow.dl_type == htons(ETH_TYPE_MPLS) ||
+               eh->eth_type == htons(ETH_TYPE_MPLS) ||
+               ctx->flow.dl_type == htons(ETH_TYPE_MPLS_MCAST) ||
+               eh->eth_type == htons(ETH_TYPE_MPLS_MCAST));
 
         memcpy(eh->eth_src, ctx->flow.dl_src, sizeof eh->eth_src);
         memcpy(eh->eth_dst, ctx->flow.dl_dst, sizeof eh->eth_dst);
@@ -5147,6 +5161,17 @@ execute_controller_action(struct action_xlate_ctx *ctx, int len,
             eth_push_vlan(packet, ctx->flow.vlan_tci);
         }
 
+        if (ctx->flow.mpls_lse) {
+            push_mpls(packet, ctx->flow.dl_type);
+            set_mpls_lse(packet, ctx->flow.mpls_lse);
+        }
+
+        if (!ctx->flow.mpls_lse &&
+            (eh->eth_type == htons(ETH_TYPE_MPLS) ||
+             eh->eth_type == htons(ETH_TYPE_MPLS_MCAST))) {
+            pop_mpls(packet, ctx->flow.dl_type);
+        }
+
         if (packet->l4) {
             if (ctx->flow.dl_type == htons(ETH_TYPE_IP)) {
                 packet_set_ipv4(packet, ctx->flow.nw_src, ctx->flow.nw_dst,
@@ -5179,6 +5204,55 @@ execute_controller_action(struct action_xlate_ctx *ctx, int len,
     ofpbuf_delete(packet);
 }
 
+static void
+compose_mpls_push_action(struct action_xlate_ctx *ctx, ovs_be16 eth_type)
+{
+    assert(eth_type == htons(ETH_TYPE_MPLS) ||
+           eth_type == htons(ETH_TYPE_MPLS_MCAST));
+
+    if (ctx->base_flow.mpls_lse != htonl(0)) {
+        ctx->flow.mpls_lse = ctx->base_flow.mpls_lse;
+        ctx->flow.mpls_lse &= ~htonl(MPLS_BOS_MASK);
+    } else {
+        ovs_be32 mpls_label, mpls_tc, mpls_ttl, mpls_bos;
+
+        ctx->flow.mpls_lse &= ~htonl(MPLS_LABEL_MASK | MPLS_TC_MASK |
+                                     MPLS_TTL_MASK | MPLS_BOS_MASK);
+        if (ctx->flow.dl_type == htons(ETH_TYPE_IPV6)) {
+            mpls_label = htonl(0x2); /* IPV6 Explicit Null. */
+        } else {
+            mpls_label = htonl(0x0); /* IPV4 Explicit Null. */
+        }
+        mpls_tc = htonl(((ctx->flow.nw_tos & IP_DSCP_MASK) >> 2)
+                        << MPLS_TC_SHIFT);
+        mpls_ttl = htonl(ctx->flow.nw_ttl << MPLS_TTL_SHIFT);
+        if (mpls_ttl == htonl(0)) {
+            mpls_ttl = htonl(0x40); /* Set default ttl for non-IP. */
+        }
+        mpls_bos = htonl(0x1 << MPLS_BOS_SHIFT);
+        ctx->flow.mpls_lse = mpls_label | mpls_tc | mpls_ttl | mpls_bos;
+        ctx->flow.encap_dl_type = ctx->flow.dl_type;
+    }
+    ctx->flow.dl_type = eth_type;
+}
+
+static void
+compose_mpls_pop_action(struct action_xlate_ctx *ctx, ovs_be16 eth_type)
+{
+    assert(ctx->flow.dl_type == htons(ETH_TYPE_MPLS) ||
+           ctx->flow.dl_type == htons(ETH_TYPE_MPLS_MCAST));
+    assert(eth_type != htons(ETH_TYPE_MPLS) &&
+           eth_type != htons(ETH_TYPE_MPLS_MCAST));
+
+    nl_msg_put_be16(ctx->odp_actions, OVS_ACTION_ATTR_POP_MPLS, eth_type);
+ 
+    if (ctx->flow.mpls_lse & htonl(MPLS_BOS_MASK)) {
+        ctx->flow.dl_type = eth_type;
+        ctx->flow.encap_dl_type = htons(0);
+    }
+    ctx->base_flow.mpls_lse = htonl(0);
+}
+
 static bool
 compose_dec_ttl(struct action_xlate_ctx *ctx, struct ofpact_cnt_ids *ids)
 {
@@ -5553,8 +5627,20 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             nxm_execute_reg_move(ofpact_get_REG_MOVE(a), &ctx->flow);
             break;
 
-        case OFPACT_REG_LOAD:
-            nxm_execute_reg_load(ofpact_get_REG_LOAD(a), &ctx->flow);
+        case OFPACT_REG_LOAD: {
+            struct ofpact_reg_load *load;
+            load = ofpact_get_REG_LOAD(a);
+            nxm_execute_reg_load(load, &ctx->flow);
+            break;
+        }
+
+        case OFPACT_PUSH_MPLS:
+            compose_mpls_push_action(ctx, ofpact_get_PUSH_MPLS(a)->ethertype);
+            break;
+
+        case OFPACT_POP_MPLS:
+            compose_mpls_pop_action(ctx, ofpact_get_POP_MPLS(a)->ethertype);
+            ctx->flow.mpls_lse = htonl(0);
             break;
 
         case OFPACT_DEC_TTL:
diff --git a/tests/automake.mk b/tests/automake.mk
index 2977f76..28327b2 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -101,6 +101,7 @@ valgrind_wrappers = \
 	tests/valgrind/test-csum \
 	tests/valgrind/test-file_name \
 	tests/valgrind/test-flows \
+	tests/valgrind/test-mpls \
 	tests/valgrind/test-hash \
 	tests/valgrind/test-heap \
 	tests/valgrind/test-hmap \
@@ -187,6 +188,10 @@ tests_test_flows_SOURCES = tests/test-flows.c
 tests_test_flows_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
 dist_check_SCRIPTS = tests/flowgen.pl
 
+noinst_PROGRAMS += tests/test-mpls
+tests_test_mpls_SOURCES = tests/test-mpls.c
+tests_test_mpls_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
+
 noinst_PROGRAMS += tests/test-hash
 tests_test_hash_SOURCES = tests/test-hash.c
 tests_test_hash_LDADD = lib/libopenvswitch.a
diff --git a/tests/odp.at b/tests/odp.at
index 9617af2..c69bc84 100644
--- a/tests/odp.at
+++ b/tests/odp.at
@@ -24,6 +24,11 @@ in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv
 in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0,proto=58,tclass=0,hlimit=128,frag=no),icmpv6(type=136,code=0),nd(target=::3,tll=00:0a:0b:0c:0d:0e)
 in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0,proto=58,tclass=0,hlimit=128,frag=no),icmpv6(type=136,code=0),nd(target=::3,sll=00:05:06:07:08:09,tll=00:0a:0b:0c:0d:0e)
 in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0806),arp(sip=1.2.3.4,tip=5.6.7.8,op=1,sha=00:0f:10:11:12:13,tha=00:14:15:16:17:18)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8847),mpls(label=100,tc=3,ttl=64,bos=1)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8847),mpls(label=100,tc=7,ttl=100,bos=1)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8847),mpls(label=100,tc=7,ttl=100,bos=0)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8848),mpls(label=1000,tc=4,ttl=200,bos=1)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8848),mpls(label=1000,tc=4,ttl=200,bos=0)
 ])
 
 (echo '# Valid forms without tun_id or VLAN header.'
@@ -39,6 +44,14 @@ in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0806),arp
 s/$/)/' odp-base.txt
 
  echo
+ echo '# Valid forms with MPLS header.'
+ sed 's/\(eth([[^)]]*),?\)/\1,eth_type(0x8847),mpls(label=100,tc=7,ttl=64,bos=1)/' odp-base.txt
+
+ echo
+ echo '# Valid forms with MPLS multicast header.'
+ sed 's/\(eth([[^)]]*),?\)/\1,eth_type(0x8848),mpls(label=100,tc=7,ttl=64,bos=1)/' odp-base.txt
+
+ echo
  echo '# Valid forms with QoS priority.'
  sed 's/^/priority(1234),/' odp-base.txt
 
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 80ba333..e380c7e 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -250,6 +250,10 @@ cookie=0x6 table=4 in_port=83 actions=load:4->NXM_NX_REG3[[]],mod_nw_src:83.83.8
 cookie=0x7 table=5 in_port=84 actions=load:5->NXM_NX_REG4[[]],load:6->NXM_NX_TUN_ID[[]],mod_nw_dst:84.84.84.84,controller,resubmit(85,6)
 cookie=0x8 table=6 in_port=85 actions=mod_tp_src:85,controller,resubmit(86,7)
 cookie=0x9 table=7 in_port=86 actions=mod_tp_dst:86,controller,controller
+cookie=0xa dl_src=40:44:44:44:44:44 actions=push_mpls:0x8847,load:10->OXM_OF_MPLS_LABEL[[]],load:3->OXM_OF_MPLS_TC[[]],controller
+cookie=0xb dl_src=50:55:55:55:55:55 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])
 
@@ -310,6 +314,83 @@ OFPT_PACKET_IN (xid=0x0): total_len=64 in_port=1 (via action) data_len=64 (unbuf
 priority:0,metadata:0,in_port:0000,tci(vlan:15,pcp:0) mac(30:33:33:33:33:33->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
 ])
 
+dnl Modified MPLS controller action.
+AT_CHECK([ovs-ofctl monitor br0 65534 -P nxm --detach --pidfile 2> ofctl_monitor.log])
+
+for i in 1 2 3; do
+    ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=40:44:44:44:44:44,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no)'
+done
+
+OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
+AT_CHECK([cat ofctl_monitor.log], [0], [dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xa total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,metadata:0,in_port:0000,tci(0) mac(40:44:44:44:44:44->50:54:00:00:00:07) type:8847,mpls(label:10,tc:3,ttl:64,bos:1)
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xa total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,metadata:0,in_port:0000,tci(0) mac(40:44:44:44:44:44->50:54:00:00:00:07) type:8847,mpls(label:10,tc:3,ttl:64,bos:1)
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xa total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,metadata:0,in_port:0000,tci(0) mac(40:44:44:44:44:44->50:54:00:00:00:07) type:8847,mpls(label:10,tc:3,ttl:64,bos:1)
+])
+
+dnl Modified MPLS actions.
+AT_CHECK([ovs-ofctl monitor br0 65534 -P nxm --detach --pidfile 2> ofctl_monitor.log])
+
+for i in 1 2 3; do
+    ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:55:55:55:55:55,dst=50:54:00:00:00:07),eth_type(0x8847),mpls(label=100,tc=7,ttl=64,bos=1),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no)'
+done
+
+OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
+AT_CHECK([cat ofctl_monitor.log], [0], [dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xb total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,metadata:0,in_port:0000,tci(0) mac(50:55:55:55:55:55->50:54:00:00:00:07) type:8847,mpls(label:1000,tc:7,ttl:64,bos:1)
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xb total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,metadata:0,in_port:0000,tci(0) mac(50:55:55:55:55:55->50:54:00:00:00:07) type:8847,mpls(label:1000,tc:7,ttl:64,bos:1)
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xb total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,metadata:0,in_port:0000,tci(0) mac(50:55:55:55:55:55->50:54:00:00:00:07) type:8847,mpls(label:1000,tc:7,ttl:64,bos:1)
+])
+
+dnl Modified MPLS ipv6 controller action.
+AT_CHECK([ovs-ofctl monitor br0 65534 -P nxm --detach --pidfile 2> ofctl_monitor.log])
+
+for i in 1 2 3; do
+    ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=70:77:77:77:77:77,dst=50:54:00:00:00:07),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0,proto=10,tclass=0x70,hlimit=128,frag=no)'
+done
+
+OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
+AT_CHECK([cat ofctl_monitor.log], [0], [dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xc total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,metadata:0,in_port:0000,tci(0) mac(70:77:77:77:77:77->50:54:00:00:00:07) type:8848,mpls(label:1000,tc:7,ttl:66,bos:1)
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xc total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,metadata:0,in_port:0000,tci(0) mac(70:77:77:77:77:77->50:54:00:00:00:07) type:8848,mpls(label:1000,tc:7,ttl:66,bos:1)
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xc total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
+priority:0,metadata:0,in_port:0000,tci(0) mac(70:77:77:77:77:77->50:54:00:00:00:07) type:8848,mpls(label:1000,tc:7,ttl:66,bos:1)
+])
+
+
+dnl Modified MPLS pop action.
+AT_CHECK([ovs-ofctl monitor br0 65534 -P nxm --detach --pidfile 2> ofctl_monitor.log])
+
+for i in 1 2 3; do
+    ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=60:66:66:66:66:66,dst=50:54:00:00:00:07),eth_type(0x8847),mpls(label=10,tc=3,ttl=100,bos=1),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no)'
+done
+
+OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
+AT_CHECK([cat ofctl_monitor.log], [0], [dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=56 in_port=1 (via action) data_len=56 (unbuffered)
+priority:0,metadata:0,in_port:0000,tci(0) mac(60:66:66:66:66:66->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) tcp_csum:0
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=56 in_port=1 (via action) data_len=56 (unbuffered)
+priority:0,metadata:0,in_port:0000,tci(0) mac(60:66:66:66:66:66->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) tcp_csum:0
+dnl
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=56 in_port=1 (via action) data_len=56 (unbuffered)
+priority:0,metadata:0,in_port:0000,tci(0) mac(60:66:66:66:66:66->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) tcp_csum:0
+])
+
 dnl Checksum TCP.
 AT_CHECK([ovs-ofctl monitor br0 65534 -P nxm --detach --no-chdir --pidfile 2> ofctl_monitor.log])
 
@@ -394,6 +475,10 @@ AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
  cookie=0x7, table=5, n_packets=2, n_bytes=120, in_port=84 actions=load:0x5->NXM_NX_REG4[[]],load:0x6->NXM_NX_TUN_ID[[]],mod_nw_dst:84.84.84.84,CONTROLLER:65535,resubmit(85,6)
  cookie=0x8, table=6, n_packets=2, n_bytes=120, in_port=85 actions=mod_tp_src:85,CONTROLLER:65535,resubmit(86,7)
  cookie=0x9, table=7, n_packets=2, n_bytes=120, in_port=86 actions=mod_tp_dst:86,CONTROLLER:65535,CONTROLLER:65535
+ cookie=0xa, n_packets=3, n_bytes=180, dl_src=40:44:44:44:44:44 actions=push_mpls:0x8847,load:0xa->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 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-mpls.c b/tests/test-mpls.c
new file mode 100644
index 0000000..b38f53c
--- /dev/null
+++ b/tests/test-mpls.c
@@ -0,0 +1,288 @@
+/*
+ * Copyright (c) 2012 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <features.h>
+#ifndef __aligned_u64
+#define __aligned_u64 __u64 __attribute__((aligned(8)))
+#endif
+#include <linux/if_packet.h>
+#include <linux/if_ether.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include "csum.h"
+#include "packets.h"
+
+#define PKT_LENGTH	     512
+#define ETH_DST_ADDR_OFF     0
+#define ETH_SRC_ADDR_OFF     ETH_DST_ADDR_OFF  + ETH_ALEN
+#define ETH_TYPE_ADDR_OFF    ETH_SRC_ADDR_OFF  + ETH_ALEN
+#define VLAN_TPID_ADDR_OFF   ETH_TYPE_ADDR_OFF
+#define VLAN_VID_ADDR_OFF    VLAN_TPID_ADDR_OFF + 2
+#define VLAN_TYPE_ADDR_OFF   VLAN_VID_ADDR_OFF + 2
+#define MPLS_HDR_ADDR_OFF    ETH_TYPE_ADDR_OFF + 2
+#define IP_HDR_ADDR_OFF      MPLS_HDR_ADDR_OFF + 4
+
+struct vlan_hdr {
+    uint16_t value;
+};
+
+static int
+create_sock (int proto)
+{
+    int sock_fd;
+
+    if ((sock_fd = socket(AF_PACKET, SOCK_RAW, proto)) == -1) {
+        perror("Error creating socket: ");
+        exit(-1);
+    }
+    return sock_fd;
+}
+
+static int
+bind_sock (char *device, int sock_fd, int protocol)
+{
+
+    struct sockaddr_ll sll;
+    struct ifreq ifr;
+    bzero(&sll, sizeof(sll));
+    bzero(&ifr, sizeof(ifr));
+
+    /* First Get the Interface Index  */
+    strncpy((char *)ifr.ifr_name, device, IFNAMSIZ);
+    if ((ioctl(sock_fd, SIOCGIFINDEX, &ifr)) == -1) {
+        printf("Error getting Interface index !\n");
+        exit(-1);
+    }
+
+    /* Bind socket to this interface */
+    sll.sll_family = AF_PACKET;
+    sll.sll_ifindex = ifr.ifr_ifindex;
+    sll.sll_protocol = htons(protocol);
+
+    if ((bind(sock_fd, (struct sockaddr *)&sll, sizeof(sll)))== -1) {
+        perror("Error binding socket to interface\n");
+        exit(-1);
+    }
+
+    return 1;
+}
+
+static int
+send_pkt (int sock_fd, uint8_t *pkt, int pkt_len)
+{
+    int sent = 0;
+
+    /* A simple write on the socket ..thats all it takes ! */
+
+    if ((sent = write(sock_fd, pkt, pkt_len)) != pkt_len) {
+        return 0;
+    }
+    return 1;
+}
+
+static void
+write_ether_type (uint8_t *pkt, uint16_t eth_type)
+{
+    ovs_be16 tmp_eth_type;
+    tmp_eth_type = htons(eth_type);
+    memcpy((void*)pkt, (void*)&tmp_eth_type, 2);
+}
+
+static void
+write_ether_hdr (uint8_t *pkt, uint16_t eth_type)
+{
+    ovs_be16 tmp_eth_type;
+    /*MAC address of the host*/
+    uint8_t src_mac[ETH_ALEN] = {0x00, 0x27, 0x13, 0x67, 0xb9, 0x9b};
+
+    /*gateway MAC address*/
+    uint8_t dest_mac[ETH_ALEN] = {0x00, 0x1f, 0x9e, 0x2a, 0x7f, 0xdd};
+
+    tmp_eth_type = htons(eth_type);
+
+    memcpy((void*)(pkt + ETH_DST_ADDR_OFF), (void*)dest_mac, ETH_ALEN);
+    memcpy((void*)(pkt + ETH_SRC_ADDR_OFF), (void*)src_mac, ETH_ALEN);
+    memcpy((void*)(pkt + ETH_TYPE_ADDR_OFF), (void*)&tmp_eth_type, 2);
+}
+
+static void
+write_vlan_hdr (uint8_t *pkt, uint16_t vid, uint16_t pcp, uint16_t id)
+{
+    struct vlan_hdr vlan_h;
+    ovs_be16 vlan_raw;
+    ovs_be16 tpid = htons(id);
+
+    vlan_h.value = ((vid << VLAN_VID_SHIFT) & VLAN_VID_MASK) |
+                   ((pcp << VLAN_PCP_SHIFT) & VLAN_PCP_MASK);
+
+    vlan_raw = htons(vlan_h.value);
+
+    memcpy((void*)pkt, (void *)&tpid, 2);
+    memcpy((void*)(pkt+2), (void *) &vlan_raw, 2);
+}
+
+static void
+write_mpls_hdr (uint8_t *pkt, uint32_t label,
+                uint32_t tc, uint32_t s, uint32_t ttl)
+{
+    struct mpls_hdr mpls_h;
+
+    mpls_h.mpls_lse = htonl(((ttl << MPLS_TTL_SHIFT) &  MPLS_TTL_MASK)  |
+                            ((tc << MPLS_TC_SHIFT) & MPLS_TC_MASK)      |
+                            ((s << MPLS_BOS_SHIFT) & MPLS_BOS_MASK) |
+                            ((label << MPLS_LABEL_SHIFT) & MPLS_LABEL_MASK));
+
+    memcpy((void*)(pkt), (void *) &mpls_h.mpls_lse, 4);
+}
+
+static void
+write_ip_hdr (uint8_t *pkt, uint16_t ip_pkt_len)
+{
+     uint8_t ip_hdr[20] = { 0x45, 0x07, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x00,
+                            0x10, 0x11, 0xa3, 0xfc,
+                            0x0a, 0x75, 0x2e, 0xc8,
+                            0x0a, 0x75, 0x2e, 0xc1};
+
+    ip_hdr[2] = (0xFF00 & ip_pkt_len) >> 8;
+    ip_hdr[3] = 0x00FF & ip_pkt_len;
+
+    memcpy((void *)(pkt), (void *) &ip_hdr, 20);
+}
+
+static void
+write_udp_hdr (uint8_t *pkt, uint16_t udp_len)
+{
+    uint8_t udp_hdr[8] = {0x0F, 0x00, 0x0F, 0x00,
+                          0x00, 0x00, 0x00, 0x00};
+    udp_hdr[4] = (0xFF00 & udp_len) >> 8;
+    udp_hdr[5] = (0x00FF & udp_len);
+    memcpy((void *)(pkt), (void *) &udp_hdr, 8);
+}
+
+static void
+write_ip_csum (uint8_t *pkt, uint16_t len)
+{
+    /* len should be just the length of the header */
+    ovs_be16 ip_csum = 0;
+
+    /* initialize the ip checksum field to 0 for
+     * purposes of calculating the header */
+    memcpy(pkt + 10, &ip_csum, 2);
+
+    /* appears to return in network byte order somehow */
+    ip_csum = csum(pkt, len);
+    memcpy(pkt + 10, &ip_csum, 2);
+}
+
+/* argv[1] is the device e.g. eth0
+   argv[2] is the number of pkts to send
+*/
+int
+main (int argc, char **argv)
+{
+
+    int sock_fd;
+    uint8_t pkt[PKT_LENGTH];
+    uint8_t *pkt_pos = pkt;
+    uint8_t *ip_pos;
+    uint32_t label = 101, tc = 4, ttl = 10;
+    uint16_t vid = 101, pcp = 4;
+    uint32_t num_of_pkts, num_labels;
+    uint16_t i = 0;
+    char *str = "FEEDFACE", type[5];
+
+    if (argc != 5) {
+        printf("usage: %s <device> <# pkts> <#labels> <type=vlan/mpls>\n", argv[0]);
+        return -1;
+    }
+
+    num_of_pkts = atoi(argv[2]);
+
+    strncpy(type, argv[argc-1], 5);
+
+    /* Set the magic data 0xfeedface */
+    for (i = 0; i < PKT_LENGTH; i+=8) {
+        memcpy((void*)(pkt + i), (void*)str, 8);
+    }
+
+    num_labels = atoi(argv[3]);
+
+    if (!strcmp(type, "vlan")) {
+        write_ether_hdr(pkt_pos, ETH_TYPE_IP);
+        pkt_pos += ETH_TYPE_ADDR_OFF;
+        for (i = 0; i < num_labels; i++) {
+            if (i == 1 || num_labels == 1) {
+                write_vlan_hdr(pkt_pos, vid++, pcp++, ETH_TYPE_VLAN);
+            }
+            else {
+                write_vlan_hdr(pkt_pos, vid++, pcp++, ETH_TYPE_VLAN);
+            }
+            pkt_pos += 4;
+        }
+        write_ether_type(pkt_pos, ETH_TYPE_IP);
+        pkt_pos+=2;
+    } else {
+        write_ether_hdr(pkt_pos, ETH_TYPE_MPLS);
+        pkt_pos += MPLS_HDR_ADDR_OFF;
+        for (i = 0; i < num_labels; i++) {
+            if (i == num_labels - 1) {
+                write_mpls_hdr(pkt_pos, label++, tc, 1, ttl++);
+            } else {
+                write_mpls_hdr(pkt_pos, label++, tc, 0, ttl++);
+            }
+            pkt_pos += 4;
+        }
+    }
+
+    ip_pos = pkt_pos;
+    write_ip_hdr(pkt_pos, PKT_LENGTH - (ip_pos - pkt));
+    pkt_pos += 20;
+
+    write_udp_hdr(pkt_pos, PKT_LENGTH -(pkt_pos - pkt));
+    pkt_pos += 8;
+
+    write_ip_csum(ip_pos, 20);
+
+    /* Create the socket */
+    sock_fd = create_sock(ETH_P_ALL);
+
+    /* Bind socket to interface */
+    bind_sock(argv[1], sock_fd, ETH_P_ALL);
+
+    while ((num_of_pkts--) > 0) {
+        if (!send_pkt(sock_fd, pkt, PKT_LENGTH)) {
+            perror("Error sending pkt");
+            printf("\n\n");
+            break;
+        }
+    }
+    printf("\nPrinting packet\n");
+    for (i = 0; i < 50; i++)
+        printf("%x ", pkt[i]);
+    if (num_of_pkts == -1)
+        printf("Packets sent successfully\n");
+
+    close(sock_fd);
+    return 0;
+}
diff --git a/utilities/ovs-dpctl.c b/utilities/ovs-dpctl.c
index 6224237..d254d01 100644
--- a/utilities/ovs-dpctl.c
+++ b/utilities/ovs-dpctl.c
@@ -895,14 +895,13 @@ dpctl_normalize_actions(int argc, char *argv[])
 
     hmap_init(&actions_per_flow);
     NL_ATTR_FOR_EACH (a, left, odp_actions.data, odp_actions.size) {
-        if (nl_attr_type(a) == OVS_ACTION_ATTR_POP_VLAN) {
+        const struct ovs_action_push_vlan *push;
+        switch(nl_attr_type(a)) {
+        case OVS_ACTION_ATTR_POP_VLAN:
             flow.vlan_tci = htons(0);
             continue;
-        }
-
-        if (nl_attr_type(a) == OVS_ACTION_ATTR_PUSH_VLAN) {
-            const struct ovs_action_push_vlan *push;
 
+        case OVS_ACTION_ATTR_PUSH_VLAN:
             push = nl_attr_get_unspec(a, sizeof *push);
             flow.vlan_tci = push->vlan_tci;
             continue;
@@ -936,6 +935,15 @@ dpctl_normalize_actions(int argc, char *argv[])
             printf("no vlan: ");
         }
 
+        if (af->flow.mpls_lse != htonl(0)) {
+            printf("mpls(label=%"PRIu32",tc=%d,ttl=%d): ",
+                   mpls_lse_to_label(af->flow.mpls_lse),
+                   mpls_lse_to_tc(af->flow.mpls_lse),
+                   mpls_lse_to_ttl(af->flow.mpls_lse));
+        } else {
+            printf("no mpls: ");
+        }
+
         ds_clear(&s);
         format_odp_actions(&s, af->actions.data, af->actions.size);
         puts(ds_cstr(&s));
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index 4f62c66..4a9fe1b 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -897,6 +897,27 @@ as necessary to match the value specified.  Valid values are between 0
 .IP \fBstrip_vlan\fR
 Strips the VLAN tag from a packet if it is present.
 .
+.IP \fBpush_mpls\fR:\fIethertype\fR
+Modifies the ethertype of a packet with the value specified. The new MPLS
+label stack entry is set with value copied from outermost MPLS label stack
+entry if present and stack bit set to 0 or MPLS label stack entry is set with
+\fBlabel\fR:\fI0 for IPv4, 2 for IPv6 or default 0\fR, \fBtc\fR:\fIleast
+significant 3 bits from IPv4 DSCP or IPv6 traffic-class or 0\fR,
+\fBstack\fR:\fI1\fR, \fBttl\fR:\fIIPv4 ttl or IPv6 hlim or default 64\fR.
+.IP
+There are some limitations in the implementation.
+push_mpls followed by pop_mpls will be synthesised to a no-op. push_mpls
+followed by another push_mpls will result in the first push_mpls being
+discarded.
+.
+.IP \fBpop_mpls\fR:\fIethertype\fR
+Strips the outermost MPLS label stack entry and modifies the ethertype of a
+packet with the value specified if MPLS stack bit is set(i.e. bottom of stack).
+.IP
+There are some limitations in the implementation.
+push_mpls followed by pop_mpls will be synthesised to a no-op. pop_mpls
+followed by another push_mpls without an intermediate push_mpls will result in the first push_mpls being discarded.
+.
 .IP \fBmod_dl_src\fB:\fImac\fR
 Sets the source Ethernet address to \fImac\fR.
 .
-- 
1.7.10.4




More information about the dev mailing list