diff mbox series

[ovs-dev,v8] Encap & Decap actions for MPLS packet type.

Message ID 20211129062205.2120-1-martinvarghesenokia@gmail.com
State Accepted
Commit 1917ace89364d366777b34467e952358d2d85b41
Headers show
Series [ovs-dev,v8] Encap & Decap actions for MPLS packet type. | expand

Checks

Context Check Description
ovsrobot/apply-robot warning apply and check: warning
ovsrobot/github-robot-_Build_and_Test success github build: passed

Commit Message

Martin Varghese Nov. 29, 2021, 6:22 a.m. UTC
From: Martin Varghese <martin.varghese@nokia.com>

The encap & decap actions are extended to support MPLS packet type.
Encap & decap actions adds and removes MPLS header at start of the
packet.

The existing PUSH MPLS & POP MPLS actions inserts & removes MPLS
header between ethernet header and the IP header. Though this behaviour
is fine for L3 VPN where an IP packet is encapsulated inside a MPLS
tunnel, it does not suffice the L2 VPN requirements. In L2 VPN the
ethernet packets must be encapsulated inside MPLS tunnel.

In this change the encap & decap actions are extended to support MPLS
packet type. The encap & decap adds and removes MPLS header at the
start of packet as depicted below.

Encapsulation:

Actions - encap(mpls(ether_type=0x8847)),encap(ethernet)

Incoming packet -> | ETH | IP | Payload |

1 Actions -  encap(mpls(ether_type=0x8847)) [Datapath action - ADD_MPLS:0x8847]

        Outgoing packet -> | MPLS | ETH | Payload|

2 Actions - encap(ethernet) [ Datapath action - push_eth ]

        Outgoing packet -> | ETH | MPLS | ETH | Payload|

Decapsulation:

Incoming packet -> | ETH | MPLS | ETH | IP | Payload |

Actions - decap(),decap(packet_type(ns=0,type=0)

1 Actions -  decap() [Datapath action - pop_eth)

        Outgoing packet -> | MPLS | ETH | IP | Payload|

2 Actions - decap(packet_type(ns=0,type=0) [Datapath action - POP_MPLS:0x6558]

        Outgoing packet -> | ETH  | IP | Payload|

Signed-off-by: Martin Varghese <martin.varghese@nokia.com>
Acked-by: Eelco Chaudron <echaudro@redhat.com>
---
Changes in v2:
   - Fixed the compilation error reported by bot.

Changes in v3:
   - Adapted the changes to allign with the kernel implementaion

Changes in v4:
   - Fixed the compilation error reported by bot.
   - Added SLOW_ACTION support for no datapath support.

Changes in v5:
   -  Code styling fixed.
   -  Given reference for packet_type field in documentation.
   -  Modified code to do recirc only for the last label.
   -  Cleaed l2,l3,l4 fields with encap.
   -  Fixed existing encap & decap tests to do set eth to outer header
   -  Added tests to verify Encap & Decap with legacy Push & Pop.
   -  Added xlate tests.
   -  Added seperate tests to inspect packet after encap & decap.
   -  Added tests for multiple mpls test cases.

Changes in v6:
   - Styling and variable naming fixed.
   - Tests added for slow path support.
   - Added comments in code.
   - Added odp test for add_mpls action.
   - Documentation added in ovs-actions.7.rst

Changes in v7:
   - Fixed Documentation.
   - Initialized variables in odp test.
   - Add test skip for no dp support.

Changes in v8:
   - Changed the encap action format from encap(mpls(ethertype=0x884*)
     to encap(mpls) & encap(mpls_mc). MPLS multicast & unicast are 
     2 different packet_types and have 2 different ethertype values.
     Hence the new format is more consistent with other encap types.
   - Fixed Documentation errors.
   - Added check for MAX labels.
   - Added tests for Max label check.
   - Added tcpdump check in tests. 
   - structures rearranged in openvswitch.h.
   - Cosmetics changes.
   - Added Ack by echaudro@redhat.com

 Documentation/ref/ovs-actions.7.rst           |  28 +-
 NEWS                                          |   1 +
 .../linux/compat/include/linux/openvswitch.h  |  30 ++
 lib/dpif-netdev.c                             |   1 +
 lib/dpif.c                                    |   1 +
 lib/odp-execute.c                             |  12 +
 lib/odp-util.c                                |  74 +++-
 lib/ofp-actions.c                             |  10 +
 lib/ofp-ed-props.c                            |   2 +
 lib/packets.c                                 |  46 +++
 lib/packets.h                                 |   2 +
 ofproto/ofproto-dpif-ipfix.c                  |   1 +
 ofproto/ofproto-dpif-sflow.c                  |   1 +
 ofproto/ofproto-dpif-xlate.c                  |  85 ++++
 ofproto/ofproto-dpif.c                        |  38 ++
 ofproto/ofproto-dpif.h                        |   4 +-
 tests/mpls-xlate.at                           | 119 ++++++
 tests/odp.at                                  |   1 +
 tests/ofp-actions.at                          |   8 +
 tests/system-traffic.at                       | 376 ++++++++++++++++++
 20 files changed, 826 insertions(+), 14 deletions(-)

Comments

0-day Robot Nov. 29, 2021, 6:38 a.m. UTC | #1
Bleep bloop.  Greetings Martin Varghese, I am a robot and I have tried out your patch.
Thanks for your contribution.

I encountered some error that I wasn't expecting.  See the details below.


checkpatch:
WARNING: Line is 110 characters long (recommended limit is 79)
#314 FILE: lib/odp-util.c:2615:
                     "add_mpls(label=%"SCNi32",tc=%"SCNd8",ttl=%"SCNd8",bos=%"SCNd8",eth_type=0x%"SCNx16")%n",

Lines checked: 1304, Warnings: 1, Errors: 0


Please check this out.  If you feel there has been an error, please email aconole@redhat.com

Thanks,
0-day Robot
Ilya Maximets Jan. 17, 2022, 1:06 a.m. UTC | #2
On 11/29/21 07:22, Martin Varghese wrote:
> From: Martin Varghese <martin.varghese@nokia.com>
> 
> The encap & decap actions are extended to support MPLS packet type.
> Encap & decap actions adds and removes MPLS header at start of the
> packet.
> 
> The existing PUSH MPLS & POP MPLS actions inserts & removes MPLS
> header between ethernet header and the IP header. Though this behaviour
> is fine for L3 VPN where an IP packet is encapsulated inside a MPLS
> tunnel, it does not suffice the L2 VPN requirements. In L2 VPN the
> ethernet packets must be encapsulated inside MPLS tunnel.
> 
> In this change the encap & decap actions are extended to support MPLS
> packet type. The encap & decap adds and removes MPLS header at the
> start of packet as depicted below.
> 
> Encapsulation:
> 
> Actions - encap(mpls(ether_type=0x8847)),encap(ethernet)
> 
> Incoming packet -> | ETH | IP | Payload |
> 
> 1 Actions -  encap(mpls(ether_type=0x8847)) [Datapath action - ADD_MPLS:0x8847]
> 
>         Outgoing packet -> | MPLS | ETH | Payload|
> 
> 2 Actions - encap(ethernet) [ Datapath action - push_eth ]
> 
>         Outgoing packet -> | ETH | MPLS | ETH | Payload|
> 
> Decapsulation:
> 
> Incoming packet -> | ETH | MPLS | ETH | IP | Payload |
> 
> Actions - decap(),decap(packet_type(ns=0,type=0)
> 
> 1 Actions -  decap() [Datapath action - pop_eth)
> 
>         Outgoing packet -> | MPLS | ETH | IP | Payload|
> 
> 2 Actions - decap(packet_type(ns=0,type=0) [Datapath action - POP_MPLS:0x6558]
> 
>         Outgoing packet -> | ETH  | IP | Payload|
> 
> Signed-off-by: Martin Varghese <martin.varghese@nokia.com>
> Acked-by: Eelco Chaudron <echaudro@redhat.com>
> ---

Thanks, Martin and Eelco!

This version looks much better with PT_MPLS_MC.  With a few cosmetic
changes, I applied the patch.

One non-cosmetic change though that I made is I added the case for
the PT_MPLS_MC to the second switch in the following code:

> @@ -8600,6 +8643,11 @@ commit_encap_decap_action(const struct flow *flow,
>              memcpy(&base_flow->dl_dst, &flow->dl_dst,
>                     sizeof(*flow) - offsetof(struct flow, dl_dst));
>              break;
> +        case PT_MPLS:
> +        case PT_MPLS_MC:
> +            commit_mpls_action(flow, base_flow, odp_actions,
> +                               pending_encap);
> +            break;
>          default:
>              /* Only the above protocols are supported for encap.
>               * The check is done at action translation. */
> @@ -8622,6 +8670,10 @@ commit_encap_decap_action(const struct flow *flow,
>                  /* pop_nsh. */
>                  odp_put_pop_nsh_action(odp_actions);
>                  break;
> +            case PT_MPLS:

               case PT_MPLS_MC:    <-- this one

> +                commit_mpls_action(flow, base_flow, odp_actions,
> +                                   pending_encap);
> +                break;
>              default:
>                  /* Checks are done during translation. */
>                  OVS_NOT_REACHED();

I think, it's needed there, but, please, let me know, if that's not
the case.

Best regards, Ilya Maximets.
Varghese, Martin (Nokia - IN/Bangalore) Jan. 17, 2022, 12:46 p.m. UTC | #3
> This version looks much better with PT_MPLS_MC.  With a few cosmetic changes, I applied the patch.

HI Ilya,
Thanks for adding the missing pieces and merging.

> One non-cosmetic change though that I made is I added the case for the PT_MPLS_MC to the second switch in the following code:

> I think, it's needed there, but, please, let me know, if that's not the case.
It is needed . I missed it.

@echaudro@redhat.com, Thanks again for guiding through.

Btw, Due to some reason I have not received the email in my gmail account.

Regards,
Martin
diff mbox series

Patch

diff --git a/Documentation/ref/ovs-actions.7.rst b/Documentation/ref/ovs-actions.7.rst
index 7224896df..936f32f4e 100644
--- a/Documentation/ref/ovs-actions.7.rst
+++ b/Documentation/ref/ovs-actions.7.rst
@@ -212,12 +212,13 @@  Unsupported packet type
   is not an L3 packet, and ``encap(nsh)`` raises this error if the current
   packet is not Ethernet, IPv4, IPv6, or NSH.
 
+  The ``decap`` action is supported only for packet types ethernet, NSH and
+  MPLS.  Openvswitch raises this error for other packet types.
   When a ``decap`` action decapsulates a packet, Open vSwitch raises this error
   if it does not support the type of inner packet.  ``decap`` of an Ethernet
   header raises this error if a VLAN header is present, ``decap`` of a NSH
   packet raises this error if the NSH inner packet is not Ethernet, IPv4, IPv6,
-  or NSH, and ``decap`` of other types of packets is unsupported and also
-  raises this error.
+  or NSH.
 
   This error terminates packet processing, retaining any previous side effects
   (e.g. output actions).  When this error arises within the execution of a
@@ -995,6 +996,8 @@  The ``encap`` action
   | ``encap(nsh([md_type=``\ *md_type*\
                  ``], [tlv(``\ *class*,\ *type*,\ *value*\ ``)]...))``
   | ``encap(ethernet)``
+  | ``encap(mpls)``
+  | ``encap(mpls_mc)``
 
 The ``encap`` action encapsulates a packet with a specified header.  It has
 variants for different kinds of encapsulation.
@@ -1018,10 +1021,20 @@  frame.  The Ethernet type is initialized to the L3 packet's type, e.g. 0x0800
 if the L3 packet is IPv4.  The Ethernet source and destination are initially
 zeroed.
 
+The ``encap(mpls)`` variant adds a MPLS header at the start of the packet.
+When encap(ethernet) is applied after this action, the ethertype of ethernet
+header will be populated with MPLS unicast ethertype (``0x8847``).
+
+The ``encap(mpls_mc)`` variant adds a MPLS header at the start of the packet.
+When encap(ethernet) is applied after this action, the ethertype of ethernet
+header will be populated with MPLS multicast ethertype (``0x8848``).
+
 **Conformance**
   This action is an Open vSwitch extension to OpenFlow 1.3 and later,
   introduced in Open vSwitch 2.8.
 
+  The MPLS support for this action is added in Open vSwitch 2.17.
+
 
 The ``decap`` action
 --------------------
@@ -1030,6 +1043,7 @@  The ``decap`` action
 
 **Syntax**:
   | ``decap``
+  | ``decap(packet_type(ns=``\*namespace,*\ ``type=``\*type*\ ``))``
 
 Removes an outermost encapsulation from the packet:
 
@@ -1042,12 +1056,22 @@  Removes an outermost encapsulation from the packet:
     and NSH inner packet types.  Other types raise unsupported packet type
     errors.
 
+  - Otherwise, if the packet is encapsulated inside a MPLS header, removes
+    the MPLS header and classifies the inner packet as mentioned in the packet
+    type argument of the decap.  The packet_type field specifies the type of
+    the packet in the format specified in OpenFlow 1.5 chapter
+    `7.2.3.11 Packet Type Match Field`.  The inner packet will be incorrectly
+    classified, if the inner packet is different from mentioned in the
+    packet_type field.
+
   - Otherwise, raises an unsupported packet type error.
 
 **Conformance**
   This action is an Open vSwitch extension to OpenFlow 1.3 and later,
   introduced in Open vSwitch 2.8.
 
+  The MPLS support for this action is added in Open vSwitch 2.17.
+
 
 Field Modification Actions
 ==========================
diff --git a/NEWS b/NEWS
index 434ee570f..c9ab310c0 100644
--- a/NEWS
+++ b/NEWS
@@ -16,6 +16,7 @@  Post-v2.16.0
    - ovs-dpctl and 'ovs-appctl dpctl/':
      * New commands 'cache-get-size' and 'cache-set-size' that allows to
        get or configure linux kernel datapath cache sizes.
+   - Encap & Decap action support for MPLS packet type.
 
 
 v2.16.0 - 16 Aug 2021
diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h
index 936644192..31966fcf0 100644
--- a/datapath/linux/compat/include/linux/openvswitch.h
+++ b/datapath/linux/compat/include/linux/openvswitch.h
@@ -754,6 +754,31 @@  struct ovs_action_push_mpls {
 	__be16 mpls_ethertype; /* Either %ETH_P_MPLS_UC or %ETH_P_MPLS_MC */
 };
 
+/* struct ovs_action_add_mpls - %OVS_ACTION_ATTR_ADD_MPLS action
+ * argument.
+ * @mpls_lse: MPLS label stack entry to push.
+ * @mpls_ethertype: Ethertype to set in the encapsulating ethernet frame.
+ * @tun_flags: MPLS tunnel attributes.
+ *
+ * The only values @mpls_ethertype should ever be given are %ETH_P_MPLS_UC and
+ * %ETH_P_MPLS_MC, indicating MPLS unicast or multicast. Other are rejected.
+ */
+struct ovs_action_add_mpls {
+	__be32 mpls_lse;
+	__be16 mpls_ethertype; /* Either %ETH_P_MPLS_UC or %ETH_P_MPLS_MC */
+	__u16 tun_flags;
+};
+
+#define OVS_MPLS_L3_TUNNEL_FLAG_MASK  (1 << 0) /* Flag to specify the place of
+						* insertion of MPLS header.
+						* When false, the MPLS header
+						* will be inserted at the start
+						* of the packet.
+						* When true, the MPLS header
+						* will be inserted at the start
+						* of the l3 header.
+						*/
+
 /**
  * struct ovs_action_push_vlan - %OVS_ACTION_ATTR_PUSH_VLAN action argument.
  * @vlan_tpid: Tag protocol identifier (TPID) to push.
@@ -1008,6 +1033,10 @@  struct check_pkt_len_arg {
  * @OVS_ACTION_ATTR_CHECK_PKT_LEN: Check the packet length and execute a set
  * of actions if greater than the specified packet length, else execute
  * another set of actions.
+ * @OVS_ACTION_ATTR_ADD_MPLS: Push a new MPLS label stack entry at the
+ * start of the packet or at the start of the l3 header depending on the value
+ * of l3 tunnel flag in the tun_flags field of OVS_ACTION_ATTR_ADD_MPLS
+ * argument.
  * @OVS_ACTION_ATTR_DROP: Explicit drop action.
  */
 
@@ -1037,6 +1066,7 @@  enum ovs_action_attr {
 	OVS_ACTION_ATTR_METER,        /* u32 meter number. */
 	OVS_ACTION_ATTR_CLONE,        /* Nested OVS_CLONE_ATTR_*.  */
 	OVS_ACTION_ATTR_CHECK_PKT_LEN, /* Nested OVS_CHECK_PKT_LEN_ATTR_*. */
+	OVS_ACTION_ATTR_ADD_MPLS,     /* struct ovs_action_add_mpls. */
 
 #ifndef __KERNEL__
 	OVS_ACTION_ATTR_TUNNEL_PUSH,   /* struct ovs_action_push_tnl*/
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index 98453a206..b80573ad1 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -8315,6 +8315,7 @@  dp_execute_cb(void *aux_, struct dp_packet_batch *packets_,
     case OVS_ACTION_ATTR_CT_CLEAR:
     case OVS_ACTION_ATTR_CHECK_PKT_LEN:
     case OVS_ACTION_ATTR_DROP:
+    case OVS_ACTION_ATTR_ADD_MPLS:
     case __OVS_ACTION_ATTR_MAX:
         OVS_NOT_REACHED();
     }
diff --git a/lib/dpif.c b/lib/dpif.c
index 38bcb47cb..0cfae1553 100644
--- a/lib/dpif.c
+++ b/lib/dpif.c
@@ -1274,6 +1274,7 @@  dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_,
     case OVS_ACTION_ATTR_UNSPEC:
     case OVS_ACTION_ATTR_CHECK_PKT_LEN:
     case OVS_ACTION_ATTR_DROP:
+    case OVS_ACTION_ATTR_ADD_MPLS:
     case __OVS_ACTION_ATTR_MAX:
         OVS_NOT_REACHED();
     }
diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index 6eeda2a61..2f4cdd92c 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -819,6 +819,7 @@  requires_datapath_assistance(const struct nlattr *a)
     case OVS_ACTION_ATTR_POP_NSH:
     case OVS_ACTION_ATTR_CT_CLEAR:
     case OVS_ACTION_ATTR_CHECK_PKT_LEN:
+    case OVS_ACTION_ATTR_ADD_MPLS:
     case OVS_ACTION_ATTR_DROP:
         return false;
 
@@ -1061,6 +1062,17 @@  odp_execute_actions(void *dp, struct dp_packet_batch *batch, bool steal,
             }
             break;
 
+        case OVS_ACTION_ATTR_ADD_MPLS: {
+            const struct ovs_action_add_mpls *mpls = nl_attr_get(a);
+            bool l3_flag =  mpls->tun_flags & OVS_MPLS_L3_TUNNEL_FLAG_MASK;
+
+            DP_PACKET_BATCH_FOR_EACH (i, packet, batch) {
+                add_mpls(packet, mpls->mpls_ethertype, mpls->mpls_lse,
+                         l3_flag);
+            }
+            break;
+        }
+
         case OVS_ACTION_ATTR_DROP:{
             const enum xlate_error *drop_reason = nl_attr_get(a);
 
diff --git a/lib/odp-util.c b/lib/odp-util.c
index fbdfc7ad8..5d8cea0dc 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -142,6 +142,7 @@  odp_action_len(uint16_t type)
     case OVS_ACTION_ATTR_PUSH_NSH: return ATTR_LEN_VARIABLE;
     case OVS_ACTION_ATTR_POP_NSH: return 0;
     case OVS_ACTION_ATTR_CHECK_PKT_LEN: return ATTR_LEN_VARIABLE;
+    case OVS_ACTION_ATTR_ADD_MPLS: return sizeof(struct ovs_action_add_mpls);
     case OVS_ACTION_ATTR_DROP: return sizeof(uint32_t);
 
     case OVS_ACTION_ATTR_UNSPEC:
@@ -1254,6 +1255,14 @@  format_odp_action(struct ds *ds, const struct nlattr *a,
     case OVS_ACTION_ATTR_CHECK_PKT_LEN:
         format_odp_check_pkt_len_action(ds, a, portno_names);
         break;
+    case OVS_ACTION_ATTR_ADD_MPLS: {
+        const struct ovs_action_add_mpls *mpls = nl_attr_get(a);
+        ds_put_cstr(ds, "add_mpls(");
+        format_mpls_lse(ds, mpls->mpls_lse);
+        ds_put_format(ds, ",eth_type=0x%"PRIx16")",
+                      ntohs(mpls->mpls_ethertype));
+        break;
+    }
     case OVS_ACTION_ATTR_DROP:
         ds_put_cstr(ds, "drop");
         break;
@@ -2595,6 +2604,29 @@  parse_odp_action__(struct parse_odp_context *context, const char *s,
             return retval;
         }
     }
+    {
+        struct ovs_action_add_mpls mpls;
+        uint32_t lse;
+        uint8_t ttl, tc, bos;
+        int n = -1;
+        uint16_t eth_type;
+
+        if (ovs_scan(s,
+                     "add_mpls(label=%"SCNi32",tc=%"SCNd8",ttl=%"SCNd8",bos=%"SCNd8",eth_type=0x%"SCNx16")%n",
+                     &lse, &tc, &ttl, &bos, &eth_type, &n)) {
+
+            mpls.mpls_ethertype = htons(eth_type);
+            mpls.mpls_lse = htonl(lse << MPLS_LABEL_SHIFT |
+                                  tc  << MPLS_TC_SHIFT |
+                                  ttl << MPLS_TTL_SHIFT |
+                                  bos << MPLS_BOS_SHIFT);
+            mpls.tun_flags = 0;
+            nl_msg_put_unspec(actions, OVS_ACTION_ATTR_ADD_MPLS,
+                              &mpls, sizeof mpls);
+
+            return n;
+        }
+    }
 
     {
         struct ovs_action_push_tnl data;
@@ -7890,7 +7922,7 @@  commit_vlan_action(const struct flow* flow, struct flow *base,
 /* Wildcarding already done at action translation time. */
 static void
 commit_mpls_action(const struct flow *flow, struct flow *base,
-                   struct ofpbuf *odp_actions)
+                   struct ofpbuf *odp_actions, bool pending_encap)
 {
     int base_n = flow_count_mpls_labels(base, NULL);
     int flow_n = flow_count_mpls_labels(flow, NULL);
@@ -7938,18 +7970,29 @@  commit_mpls_action(const struct flow *flow, struct flow *base,
     /* If, after the above popping and setting, there are more LSEs in flow
      * than base then some LSEs need to be pushed. */
     while (base_n < flow_n) {
-        struct ovs_action_push_mpls *mpls;
 
-        mpls = nl_msg_put_unspec_zero(odp_actions,
-                                      OVS_ACTION_ATTR_PUSH_MPLS,
-                                      sizeof *mpls);
-        mpls->mpls_ethertype = flow->dl_type;
-        mpls->mpls_lse = flow->mpls_lse[flow_n - base_n - 1];
+        if (pending_encap) {
+             struct ovs_action_add_mpls *mpls;
+
+             mpls = nl_msg_put_unspec_zero(odp_actions,
+                                           OVS_ACTION_ATTR_ADD_MPLS,
+                                           sizeof *mpls);
+             mpls->mpls_ethertype = flow->dl_type;
+             mpls->mpls_lse = flow->mpls_lse[flow_n - base_n - 1];
+        } else {
+             struct ovs_action_push_mpls *mpls;
+
+             mpls = nl_msg_put_unspec_zero(odp_actions,
+                                           OVS_ACTION_ATTR_PUSH_MPLS,
+                                           sizeof *mpls);
+             mpls->mpls_ethertype = flow->dl_type;
+             mpls->mpls_lse = flow->mpls_lse[flow_n - base_n - 1];
+        }
         /* Update base flow's MPLS stack, but do not clear L3.  We need the L3
          * headers if the flow is restored later due to returning from a patch
          * port or group bucket. */
-        flow_push_mpls(base, base_n, mpls->mpls_ethertype, NULL, false);
-        flow_set_mpls_lse(base, 0, mpls->mpls_lse);
+        flow_push_mpls(base, base_n, flow->dl_type, NULL, false);
+        flow_set_mpls_lse(base, 0, flow->mpls_lse[flow_n - base_n - 1]);
         base_n++;
     }
 }
@@ -8600,6 +8643,11 @@  commit_encap_decap_action(const struct flow *flow,
             memcpy(&base_flow->dl_dst, &flow->dl_dst,
                    sizeof(*flow) - offsetof(struct flow, dl_dst));
             break;
+        case PT_MPLS:
+        case PT_MPLS_MC:
+            commit_mpls_action(flow, base_flow, odp_actions,
+                               pending_encap);
+            break;
         default:
             /* Only the above protocols are supported for encap.
              * The check is done at action translation. */
@@ -8622,6 +8670,10 @@  commit_encap_decap_action(const struct flow *flow,
                 /* pop_nsh. */
                 odp_put_pop_nsh_action(odp_actions);
                 break;
+            case PT_MPLS:
+                commit_mpls_action(flow, base_flow, odp_actions,
+                                   pending_encap);
+                break;
             default:
                 /* Checks are done during translation. */
                 OVS_NOT_REACHED();
@@ -8667,7 +8719,7 @@  commit_odp_actions(const struct flow *flow, struct flow *base,
     /* Make packet a non-MPLS packet before committing L3/4 actions,
      * which would otherwise do nothing. */
     if (eth_type_mpls(base->dl_type) && !eth_type_mpls(flow->dl_type)) {
-        commit_mpls_action(flow, base, odp_actions);
+        commit_mpls_action(flow, base, odp_actions, false);
         mpls_done = true;
     }
     commit_set_nsh_action(flow, base, odp_actions, wc, use_masked);
@@ -8675,7 +8727,7 @@  commit_odp_actions(const struct flow *flow, struct flow *base,
     commit_set_port_action(flow, base, odp_actions, wc, use_masked);
     slow2 = commit_set_icmp_action(flow, base, odp_actions, wc);
     if (!mpls_done) {
-        commit_mpls_action(flow, base, odp_actions);
+        commit_mpls_action(flow, base, odp_actions, false);
     }
     commit_vlan_action(flow, base, odp_actions, wc);
     commit_set_priority_action(flow, base, odp_actions, wc, use_masked);
diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
index ecf914eac..68a846a24 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -4468,6 +4468,8 @@  decode_NXAST_RAW_ENCAP(const struct nx_action_encap *nae,
     switch (ntohl(nae->new_pkt_type)) {
     case PT_ETH:
     case PT_NSH:
+    case PT_MPLS:
+    case PT_MPLS_MC:
         /* Add supported encap header types here. */
         break;
     default:
@@ -4519,6 +4521,10 @@  parse_encap_header(const char *hdr, ovs_be32 *packet_type)
         *packet_type = htonl(PT_ETH);
     } else if (strcmp(hdr, "nsh") == 0) {
         *packet_type = htonl(PT_NSH);
+    } else if (strcmp(hdr, "mpls") == 0) {
+        *packet_type = htonl(PT_MPLS);
+    } else if (strcmp(hdr, "mpls_mc") == 0) {
+        *packet_type = htonl(PT_MPLS_MC);
     } else {
         return false;
     }
@@ -4600,6 +4606,10 @@  format_encap_pkt_type(const ovs_be32 pkt_type)
         return "ethernet";
     case PT_NSH:
         return "nsh";
+    case PT_MPLS:
+        return "mpls";
+    case PT_MPLS_MC:
+        return "mpls_mc";
     default:
         return "UNKNOWN";
     }
diff --git a/lib/ofp-ed-props.c b/lib/ofp-ed-props.c
index 02a9235d5..d0649da02 100644
--- a/lib/ofp-ed-props.c
+++ b/lib/ofp-ed-props.c
@@ -153,6 +153,8 @@  parse_ed_prop_class(const char *str OVS_UNUSED,
         *prop_class = OFPPPC_BASIC;
     } else if (!strcmp(str,"mpls")) {
         *prop_class = OFPPPC_MPLS;
+    } else if (!strcmp(str,"mpls_mc")) {
+        *prop_class = OFPPPC_MPLS;
     } else if (!strcmp(str,"gre")) {
         *prop_class = OFPPPC_GRE;
     } else if (!strcmp(str,"gtp")) {
diff --git a/lib/packets.c b/lib/packets.c
index 4a7643c5d..7ea50ffb2 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -418,6 +418,40 @@  push_mpls(struct dp_packet *packet, ovs_be16 ethtype, ovs_be32 lse)
     pkt_metadata_init_conn(&packet->md);
 }
 
+void
+add_mpls(struct dp_packet *packet, ovs_be16 ethtype, ovs_be32 lse,
+         bool l3_encap)
+{
+    if (!eth_type_mpls(ethtype)) {
+        return;
+    }
+
+    if (!l3_encap) {
+        ovs_be32 *header = dp_packet_push_uninit(packet, MPLS_HLEN);
+
+        *header = lse;
+        packet->l2_5_ofs = 0;
+        packet->packet_type = PACKET_TYPE_BE(OFPHTN_ETHERTYPE,
+                                             ntohs(ethtype));
+    } else {
+        size_t len;
+        char *header;
+
+        if (!is_mpls(packet)) {
+            /* Set MPLS label stack offset. */
+            packet->l2_5_ofs = packet->l3_ofs;
+        }
+        set_ethertype(packet, ethtype);
+
+        /* Push new MPLS shim header onto packet. */
+        len = packet->l2_5_ofs;
+        header = dp_packet_resize_l2_5(packet, MPLS_HLEN);
+        memmove(header, header + MPLS_HLEN, len);
+        memcpy(header + len, &lse, sizeof lse);
+    }
+    pkt_metadata_init_conn(&packet->md);
+}
+
 /* If 'packet' is an MPLS packet, removes its outermost MPLS label stack entry.
  * If the label that was removed was the only MPLS label, changes 'packet''s
  * Ethertype to 'ethtype' (which ordinarily should not be an MPLS
@@ -440,6 +474,18 @@  pop_mpls(struct dp_packet *packet, ovs_be16 ethtype)
         /* Invalidate offload flags as they are not valid after
          * decapsulation of MPLS header. */
         dp_packet_reset_offload(packet);
+
+        /* packet_type must be reset for the MPLS packets with no l2 header */
+        if (!len) {
+            if (ethtype == htons(ETH_TYPE_TEB)) {
+                /* The inner packet must be classsified as ethernet if the
+                 * ethtype is ETH_TYPE_TEB. */
+                packet->packet_type = htonl(PT_ETH);
+            } else {
+                packet->packet_type = PACKET_TYPE_BE(OFPHTN_ETHERTYPE,
+                                                     ntohs(ethtype));
+            }
+        }
     }
 }
 
diff --git a/lib/packets.h b/lib/packets.h
index e8bdf08a0..5bdf6e4bb 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -356,6 +356,8 @@  void set_mpls_lse_label(ovs_be32 *lse, ovs_be32 label);
 void set_mpls_lse_bos(ovs_be32 *lse, uint8_t bos);
 ovs_be32 set_mpls_lse_values(uint8_t ttl, uint8_t tc, uint8_t bos,
                              ovs_be32 label);
+void add_mpls(struct dp_packet *packet, ovs_be16 ethtype, ovs_be32 lse,
+              bool l3_encap);
 
 /* Example:
  *
diff --git a/ofproto/ofproto-dpif-ipfix.c b/ofproto/ofproto-dpif-ipfix.c
index 796eb6f88..9280e008e 100644
--- a/ofproto/ofproto-dpif-ipfix.c
+++ b/ofproto/ofproto-dpif-ipfix.c
@@ -3018,6 +3018,7 @@  dpif_ipfix_read_actions(const struct flow *flow,
         case OVS_ACTION_ATTR_CHECK_PKT_LEN:
         case OVS_ACTION_ATTR_UNSPEC:
         case OVS_ACTION_ATTR_DROP:
+        case OVS_ACTION_ATTR_ADD_MPLS:
         case __OVS_ACTION_ATTR_MAX:
         default:
             break;
diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
index 864c136b5..30e7caf54 100644
--- a/ofproto/ofproto-dpif-sflow.c
+++ b/ofproto/ofproto-dpif-sflow.c
@@ -1226,6 +1226,7 @@  dpif_sflow_read_actions(const struct flow *flow,
         case OVS_ACTION_ATTR_UNSPEC:
         case OVS_ACTION_ATTR_CHECK_PKT_LEN:
         case OVS_ACTION_ATTR_DROP:
+        case OVS_ACTION_ATTR_ADD_MPLS:
         case __OVS_ACTION_ATTR_MAX:
         default:
             break;
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 9d336bc6a..cf35f5c68 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -6420,6 +6420,47 @@  rewrite_flow_encap_ethernet(struct xlate_ctx *ctx,
     }
 }
 
+static void
+rewrite_flow_encap_mpls(struct xlate_ctx *ctx,
+                        const struct ofpact_encap *encap,
+                        struct flow *flow,
+                        struct flow_wildcards *wc)
+{
+    ovs_be16 ether_type = pt_ns_type_be(encap->new_pkt_type);
+    int n;
+
+    n = flow_count_mpls_labels(flow, ctx->wc);
+    if (n < FLOW_MAX_MPLS_LABELS) {
+        wc->masks.packet_type = OVS_BE32_MAX;
+
+       /* If the current packet is already a MPLS packet with ethernet header
+        * the existing MPLS states must be cleared before the encap MPLS action
+        * is applied. */
+       if (flow->packet_type == htonl(PT_ETH) &&
+           flow->dl_type == htons(ETH_TYPE_MPLS)) {
+           memset(&ctx->wc->masks.mpls_lse, 0x0,
+                  sizeof *wc->masks.mpls_lse * FLOW_MAX_MPLS_LABELS);
+           memset(&flow->mpls_lse, 0x0, sizeof *flow->mpls_lse *
+                  FLOW_MAX_MPLS_LABELS);
+           memset(&ctx->base_flow.mpls_lse, 0x0,
+                  sizeof *ctx->base_flow.mpls_lse * FLOW_MAX_MPLS_LABELS);
+       }
+       flow->packet_type = encap->new_pkt_type;
+       flow_push_mpls(flow, n, ether_type, ctx->wc, true);
+       flow->dl_src = eth_addr_zero;
+       flow->dl_dst = eth_addr_zero;
+    } else {
+        if (ctx->xin->packet != NULL) {
+            xlate_report_error(ctx, "dropping packet on which an encap MPLS "
+                               "action can't be performed as it would have "
+                               "more MPLS LSEs than the %d supported.",
+                               FLOW_MAX_MPLS_LABELS);
+        }
+        ctx->error = XLATE_TOO_MANY_MPLS_LABELS;
+        return;
+    }
+}
+
 /* For an MD2 NSH header returns a pointer to an ofpbuf with the encoded
  * MD2 TLVs provided as encap properties to the encap operation. This
  * will be stored as encap_data in the ctx and copied into the push_nsh
@@ -6551,6 +6592,13 @@  xlate_generic_encap_action(struct xlate_ctx *ctx,
         case PT_NSH:
             encap_data = rewrite_flow_push_nsh(ctx, encap, flow, wc);
             break;
+        case PT_MPLS:
+        case PT_MPLS_MC:
+            rewrite_flow_encap_mpls(ctx, encap, flow, wc);
+            if (!ctx->xbridge->support.add_mpls) {
+                ctx->xout->slow |= SLOW_ACTION;
+            }
+            break;
         default:
             /* New packet type was checked during decoding. */
             OVS_NOT_REACHED();
@@ -6622,6 +6670,43 @@  xlate_generic_decap_action(struct xlate_ctx *ctx,
             ctx->pending_decap = true;
             /* Trigger recirculation. */
             return true;
+        case PT_MPLS: {
+            int n;
+            ovs_be16 ethertype;
+
+            flow->packet_type = decap->new_pkt_type;
+            ethertype = pt_ns_type_be(flow->packet_type);
+
+            n = flow_count_mpls_labels(flow, ctx->wc);
+            if (!ethertype) {
+                ethertype = htons(ETH_TYPE_TEB);
+            }
+            if (flow_pop_mpls(flow, n, ethertype, ctx->wc)) {
+                if (!ctx->xbridge->support.add_mpls) {
+                   ctx->xout->slow |= SLOW_ACTION;
+                }
+                ctx->pending_decap = true;
+                if (n == 1) {
+                    /* Trigger recirculation. */
+                    return true;
+                } else {
+                    return false;
+                }
+            } else if (n >= FLOW_MAX_MPLS_LABELS) {
+                if (ctx->xin->packet != NULL) {
+                    xlate_report_error(ctx, "dropping packet on which an "
+                                       "MPLS decap can't be performed as "
+                                       "it has more MPLS LSEs than the %d "
+                                       "supported.",
+                                       FLOW_MAX_MPLS_LABELS);
+                }
+                ctx->error = XLATE_TOO_MANY_MPLS_LABELS;
+                ofpbuf_clear(ctx->odp_actions);
+                return false;
+            } else {
+                return false;
+            }
+        }
         default:
             /* Error handling: drop packet. */
             xlate_report_debug(
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index cba49a99e..c65b992a7 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -1538,6 +1538,42 @@  check_nd_extensions(struct dpif_backer *backer)
 
     return !error;
 }
+/* Tests whether 'backer''s datapath supports the
+ * OVS_ACTION_ATTR_ADD_MPLS action. */
+static bool
+check_add_mpls(struct dpif_backer *backer)
+{
+    struct odputil_keybuf keybuf;
+    struct ofpbuf actions;
+    struct ofpbuf key;
+    struct flow flow;
+    bool supported;
+
+    struct odp_flow_key_parms odp_parms = {
+        .flow = &flow,
+        .probe = true,
+    };
+
+    memset(&flow, 0, sizeof flow);
+    ofpbuf_use_stack(&key, &keybuf, sizeof keybuf);
+    odp_flow_key_from_flow(&odp_parms, &key);
+    ofpbuf_init(&actions, 64);
+
+    struct ovs_action_add_mpls *mpls;
+
+    mpls = nl_msg_put_unspec_zero(&actions,
+                                  OVS_ACTION_ATTR_ADD_MPLS,
+                                  sizeof *mpls);
+    mpls->mpls_ethertype = htons(ETH_TYPE_MPLS);
+
+    supported = dpif_probe_feature(backer->dpif, "add_mpls", &key,
+                                   &actions, NULL);
+    ofpbuf_uninit(&actions);
+    VLOG_INFO("%s: Datapath %s add_mpls action",
+              dpif_name(backer->dpif),
+              supported ? "supports" : "does not support");
+    return supported;
+}
 
 #define CHECK_FEATURE__(NAME, SUPPORT, FIELD, VALUE, ETHTYPE)               \
 static bool                                                                 \
@@ -1609,6 +1645,7 @@  check_support(struct dpif_backer *backer)
     backer->rt_support.lb_output_action =
         dpif_supports_lb_output_action(backer->dpif);
     backer->rt_support.ct_zero_snat = dpif_supports_ct_zero_snat(backer);
+    backer->rt_support.add_mpls = check_add_mpls(backer);
 
     /* Flow fields. */
     backer->rt_support.odp.ct_state = check_ct_state(backer);
@@ -5625,6 +5662,7 @@  get_datapath_cap(const char *datapath_type, struct smap *cap)
              s.explicit_drop_action ? "true" :"false");
     smap_add(cap, "lb_output_action", s.lb_output_action ? "true" : "false");
     smap_add(cap, "ct_zero_snat", s.ct_zero_snat ? "true" : "false");
+    smap_add(cap, "add_mpls", s.add_mpls ? "true" : "false");
 }
 
 /* Gets timeout policy name in 'backer' based on 'zone', 'dl_type' and
diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h
index 191cfcb0d..a83cc42a9 100644
--- a/ofproto/ofproto-dpif.h
+++ b/ofproto/ofproto-dpif.h
@@ -207,7 +207,9 @@  struct group_dpif *group_dpif_lookup(struct ofproto_dpif *,
     DPIF_SUPPORT_FIELD(bool, lb_output_action, "Optimized Balance TCP mode")\
                                                                             \
     /* True if the datapath supports all-zero IP SNAT. */                   \
-    DPIF_SUPPORT_FIELD(bool, ct_zero_snat, "Conntrack all-zero IP SNAT")
+    DPIF_SUPPORT_FIELD(bool, ct_zero_snat, "Conntrack all-zero IP SNAT")    \
+    /* True if the datapath supports add_mpls action   */                   \
+    DPIF_SUPPORT_FIELD(bool, add_mpls, "MPLS Label add")
 
 
 /* Stores the various features which the corresponding backer supports. */
diff --git a/tests/mpls-xlate.at b/tests/mpls-xlate.at
index aafa89ba6..7474becc8 100644
--- a/tests/mpls-xlate.at
+++ b/tests/mpls-xlate.at
@@ -207,3 +207,122 @@  AT_CHECK([tail -1 stdout], [0],
 
 OVS_VSWITCHD_STOP
 AT_CLEANUP
+
+AT_SETUP([Encap Decap MPLS xlate action])
+
+OVS_VSWITCHD_START(
+  [add-port br0 p1 -- set Interface p1 type=dummy ofport_request=1 -- \
+   add-port br0 p2 -- set Interface p2 type=patch \
+                                       options:peer=p3 ofport_request=2 -- \
+   add-br br1 -- \
+   set bridge br1 other-config:hwaddr=aa:66:aa:66:00:00 -- \
+   set bridge br1 datapath-type=dummy other-config:datapath-id=1234 \
+                  fail-mode=secure -- \
+   add-port br1 p3 -- set Interface p3 type=patch \
+                                       options:peer=p2 ofport_request=3 -- \
+   add-port br1 p4 -- set Interface p4 type=dummy ofport_request=4])
+
+AT_CHECK([ovs-appctl dpif/show], [0], [dnl
+dummy@ovs-dummy: hit:0 missed:0
+  br0:
+    br0 65534/100: (dummy-internal)
+    p1 1/1: (dummy)
+    p2 2/none: (patch: peer=p3)
+  br1:
+    br1 65534/101: (dummy-internal)
+    p3 3/none: (patch: peer=p2)
+    p4 4/4: (dummy)
+])
+
+AT_CHECK([ovs-ofctl del-flows br0])
+AT_CHECK([ovs-ofctl -O OpenFlow13 add-flow br0 "in_port=p1,actions=encap(mpls),encap(ethernet),set_field:00:00:00:00:00:02->dl_dst,set_field:00:00:00:00:00:01->dl_src,output:p2"])
+AT_CHECK([ovs-ofctl -O OpenFlow13 add-flow br1 "in_port=p3,dl_type=0x8847 actions=decap(),decap(packet_type(ns=0,type=0)),output:p4"])
+
+# Now send two real ICMP echo request packets in on port p1
+
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 1e2ce92a669e3a6dd2099cab0800450000548a53400040011addc0a80a0ac0a80a1e08006f200a4d0001fc509a58000000002715020000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637] ,[0], [ignore])
+
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 1e2ce92a669e3a6dd2099cab0800450000548a53400040011addc0a80a0ac0a80a1e08006f200a4d0001fc509a58000000002715020000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637] ,[0], [ignore])
+
+AT_CHECK([ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v ipv6 |sort], [0],
+[flow-dump from the main thread:
+recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(src=3a:6d:d2:09:9c:ab,dst=1e:2c:e9:2a:66:9e),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:add_mpls(label=0,tc=0,ttl=64,bos=1,eth_type=0x8847),push_eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),pop_eth,pop_mpls(eth_type=0x6558),recirc(0x1)
+recirc_id(0x1),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:4
+])
+
+AT_CHECK(ovs-appctl dpif/set-dp-features br0 add_mpls false)
+
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 1e2ce92a669e3a6dd2099cab0800450000548a53400040011addc0a80a0ac0a80a1e08006f200a4d0001fc509a58000000002715020000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637] ,[0], [ignore])
+
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 1e2ce92a669e3a6dd2099cab0800450000548a53400040011addc0a80a0ac0a80a1e08006f200a4d0001fc509a58000000002715020000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637] ,[0], [ignore])
+
+AT_CHECK([ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v ipv6 |sort], [0],
+[flow-dump from the main thread:
+recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(src=3a:6d:d2:09:9c:ab,dst=1e:2c:e9:2a:66:9e),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:userspace(pid=0,slow_path(action))
+recirc_id(0x2),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:4
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([Encap MPLS xlate action - max labels])
+
+OVS_VSWITCHD_START([dnl
+    set bridge br0 datapath_type=dummy \
+        protocols=OpenFlow10,OpenFlow13,OpenFlow14,OpenFlow15 -- \
+    add-port br0 p1 -- set Interface p1 type=dummy ofport_request=1 -- \
+    add-port br0 p2 -- set Interface p2 type=dummy ofport_request=2])
+
+AT_CHECK([ovs-appctl dpif/show], [0], [dnl
+dummy@ovs-dummy: hit:0 missed:0
+  br0:
+    br0 65534/100: (dummy-internal)
+    p1 1/1: (dummy)
+    p2 2/2: (dummy)
+])
+
+AT_CHECK([ovs-ofctl del-flows br0])
+AT_CHECK([ovs-ofctl -O OpenFlow13 add-flow br0 "in_port=p1,actions=encap(mpls),set_field:1->mpls_label,encap(mpls),set_field:2->mpls_label,encap(mpls),set_field:3->mpls_label,encap(mpls),set_field:4->mpls_label,encap(ethernet),set_field:00:00:00:00:00:02->dl_dst,set_field:00:00:00:00:00:01->dl_src,output:p2"])
+
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 1e2ce92a669e3a6dd2099cab0800450000548a53400040011addc0a80a0ac0a80a1e08006f200a4d0001fc509a58000000002715020000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637] ,[0], [ignore])
+
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 1e2ce92a669e3a6dd2099cab0800450000548a53400040011addc0a80a0ac0a80a1e08006f200a4d0001fc509a58000000002715020000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637] ,[0], [ignore])
+
+AT_CHECK([ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v ipv6 |sort], [0],
+[flow-dump from the main thread:
+recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:drop
+])
+
+OVS_VSWITCHD_STOP(["/ofproto_dpif_xlate|WARN|dropping packet on which an encap MPLS action can't be performed as it would have more MPLS LSEs than the 3 supported. on bridge br0 while processing mpls,in_port=1,vlan_tci=0x0000,dl_src=3a:6d:d2:09:9c:ab,dl_dst=1e:2c:e9:2a:66:9e,mpls_label=3,mpls_tc=0,mpls_ttl=64,mpls_bos=0,mpls_lse1=8256,mpls_lse2=4416/d"])
+AT_CLEANUP
+
+AT_SETUP([Decap MPLS xlate action - max labels])
+
+OVS_VSWITCHD_START([dnl
+    set bridge br0 datapath_type=dummy \
+        protocols=OpenFlow10,OpenFlow13,OpenFlow14,OpenFlow15 -- \
+    add-port br0 p1 -- set Interface p1 type=dummy ofport_request=1 -- \
+    add-port br0 p2 -- set Interface p2 type=dummy ofport_request=2])
+
+AT_CHECK([ovs-appctl dpif/show], [0], [dnl
+dummy@ovs-dummy: hit:0 missed:0
+  br0:
+    br0 65534/100: (dummy-internal)
+    p1 1/1: (dummy)
+    p2 2/2: (dummy)
+])
+
+AT_CHECK([ovs-ofctl del-flows br0])
+AT_CHECK([ovs-ofctl -Oopenflow13 add-flow br0 "table=0,priority=100,dl_type=0x8847 actions=decap(),decap(packet_type(ns=0,type=0)),output:p2"])
+
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 00000000000200000000000188470000204000002040000020400000204036b1ee7c010236b1ee7c010308004500005403444000400121610a0101010a0101020800efac7ce400035b2c1f6100000000500b020000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637] ,[0], [ignore])
+
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 00000000000200000000000188470000204000002040000020400000204036b1ee7c010236b1ee7c010308004500005403444000400121610a0101010a0101020800efac7ce400035b2c1f6100000000500b020000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637] ,[0], [ignore])
+
+AT_CHECK([ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v ipv6 |sort], [0],
+[flow-dump from the main thread:
+recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x8847),mpls(label=2/0x0,tc=0/0,ttl=64/0x0,bos=0/1,label=2/0x0,tc=0/0,ttl=64/0x0,bos=0/1,label=2/0x0,tc=0/0,ttl=64/0x0,bos=0/1), packets:1, bytes:128, used:0.0s, actions:drop
+])
+
+OVS_VSWITCHD_STOP(["/ofproto_dpif_xlate|WARN|dropping packet on which an MPLS decap can't be performed as it has more MPLS LSEs than the 3 supported. on bridge br0 while processing packet_type=(1,0x8847),in_port=1,mpls_label=2,mpls_tc=0,mpls_ttl=64,mpls_bos=0,mpls_lse1=8256,mpls_lse2=8256/d"])
+AT_CLEANUP
diff --git a/tests/odp.at b/tests/odp.at
index 07a5cfe39..4d08c59ca 100644
--- a/tests/odp.at
+++ b/tests/odp.at
@@ -384,6 +384,7 @@  check_pkt_len(size=200,gt(drop),le(5))
 check_pkt_len(size=200,gt(ct(nat)),le(drop))
 check_pkt_len(size=200,gt(set(eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15))),le(set(eth(src=00:01:02:03:04:06,dst=10:11:12:13:14:16))))
 lb_output(1)
+add_mpls(label=200,tc=7,ttl=64,bos=1,eth_type=0x8847)
 ])
 AT_CHECK_UNQUOTED([ovstest test-odp parse-actions < actions.txt], [0],
   [`cat actions.txt`
diff --git a/tests/ofp-actions.at b/tests/ofp-actions.at
index ef4898abb..9d820eba6 100644
--- a/tests/ofp-actions.at
+++ b/tests/ofp-actions.at
@@ -780,6 +780,14 @@  dnl NSH encap with non-zero padding.
 # 23: 56 -> 00
 ffff 0018 00002320 002e 0000 0001894f 0004 01 05 01 123456
 
+dnl Check mpls encap
+# actions=encap(mpls)
+ffff 0010 00002320 002e 0000 00018847
+
+dnl Check mpls encap
+# actions=encap(mpls_mc)
+ffff 0010 00002320 002e 0000 00018848
+
 ])
 sed '/^[[#&]]/d' < test-data > input.txt
 sed -n 's/^# //p; /^$/p' < test-data > expout
diff --git a/tests/system-traffic.at b/tests/system-traffic.at
index a69896d33..b96425d1e 100644
--- a/tests/system-traffic.at
+++ b/tests/system-traffic.at
@@ -1087,6 +1087,382 @@  NS_CHECK_EXEC([at_ns1], [ping -q -c 3 -i 0.3 -w 2 10.1.1.1 | FORMAT_PING], [0],
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
 
+AT_SETUP([mpls - encap header dp-support])
+AT_SKIP_IF([test $HAVE_TCPDUMP = no])
+OVS_TRAFFIC_VSWITCHD_START()
+
+AT_SKIP_IF([! ovs-appctl dpif/show-dp-features br0 2>&1 | grep "MPLS Label add: Yes" >/dev/null])
+
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24", 36:b1:ee:7c:01:03)
+ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24", 36:b1:ee:7c:01:02)
+
+dnl The flow will encap a mpls header to the ip packet
+dnl eth/ip/icmp --> OVS --> eth/mpls/eth/ip/icmp
+AT_CHECK([ovs-ofctl -Oopenflow13 add-flow br0 "table=0,priority=100,dl_type=0x0800 actions=encap(mpls),set_mpls_label:2,encap(ethernet),set_field:00:00:00:00:00:02->dl_dst,set_field:00:00:00:00:00:01->dl_src,ovs-p1"])
+
+rm -rf p1.pcap
+NS_CHECK_EXEC([at_ns1], [tcpdump -l -n -xx -U -i p1 > p1.pcap &])
+sleep 1
+
+dnl The hex dump is a icmp packet. pkt=eth/ip/icmp
+dnl The packet is sent from p0(at_ns0) interface directed to
+dnl p1(at_ns1) interface
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 36 b1 ee 7c 01 02 36 b1 ee 7c 01 03 08 00 45 00 00 54 03 44 40 00 40 01 21 61 0a 01 01 01 0a 01 01 02 08 00 ef ac 7c e4 00 03 5b 2c 1f 61 00 00 00 00 50 0b 02 00 00 00 00 00 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37  > /dev/null])
+
+dnl Check the expected mpls encapsulated packet on the egress interface
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0000:  *0000 *0000 *0002 *0000 *0000 *0001 *8847 *0000" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0010:  *2140 *36b1 *ee7c *0102 *36b1 *ee7c *0103 *0800" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0020:  *4500 *0054 *0344 *4000 *4001 *2161 *0a01 *0101" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0030:  *0a01 *0102 *0800 *efac *7ce4 *0003 *5b2c *1f61" 2>&1 1>/dev/null])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([mpls - encap header slow-path])
+AT_SKIP_IF([test $HAVE_TCPDUMP = no])
+OVS_TRAFFIC_VSWITCHD_START()
+
+AT_CHECK(ovs-appctl dpif/set-dp-features br0 add_mpls false)
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24", 36:b1:ee:7c:01:03)
+ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24", 36:b1:ee:7c:01:02)
+
+dnl The flow will encap a mpls header to the ip packet
+dnl eth/ip/icmp --> OVS --> eth/mpls/eth/ip/icmp
+AT_CHECK([ovs-ofctl -Oopenflow13 add-flow br0 "table=0,priority=100,dl_type=0x0800 actions=encap(mpls),set_mpls_label:2,encap(ethernet),set_field:00:00:00:00:00:02->dl_dst,set_field:00:00:00:00:00:01->dl_src,ovs-p1"])
+
+rm -rf p1.pcap
+NS_CHECK_EXEC([at_ns1], [tcpdump -l -n -xx -U -i p1 > p1.pcap &])
+sleep 1
+
+dnl The hex dump is a icmp packet. pkt=eth/ip/icmp
+dnl The packet is sent from p0(at_ns0) interface directed to
+dnl p1(at_ns1) interface
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 36 b1 ee 7c 01 02 36 b1 ee 7c 01 03 08 00 45 00 00 54 03 44 40 00 40 01 21 61 0a 01 01 01 0a 01 01 02 08 00 ef ac 7c e4 00 03 5b 2c 1f 61 00 00 00 00 50 0b 02 00 00 00 00 00 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37  > /dev/null])
+
+dnl Check the expected mpls encapsulated packet on the egress interface
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0000:  *0000 *0000 *0002 *0000 *0000 *0001 *8847 *0000" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0010:  *2140 *36b1 *ee7c *0102 *36b1 *ee7c *0103 *0800" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0020:  *4500 *0054 *0344 *4000 *4001 *2161 *0a01 *0101" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0030:  *0a01 *0102 *0800 *efac *7ce4 *0003 *5b2c *1f61" 2>&1 1>/dev/null])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([mpls_mc - encap header dp-support])
+AT_SKIP_IF([test $HAVE_TCPDUMP = no])
+OVS_TRAFFIC_VSWITCHD_START()
+
+AT_SKIP_IF([! ovs-appctl dpif/show-dp-features br0 2>&1 | grep "MPLS Label add: Yes" >/dev/null])
+
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24", 36:b1:ee:7c:01:03)
+ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24", 36:b1:ee:7c:01:02)
+
+dnl The flow will encap a mpls header to the ip packet
+dnl eth/ip/icmp --> OVS --> eth/mpls/eth/ip/icmp
+AT_CHECK([ovs-ofctl -Oopenflow13 add-flow br0 "table=0,priority=100,dl_type=0x0800 actions=encap(mpls_mc),set_mpls_label:2,encap(ethernet),set_field:00:00:00:00:00:02->dl_dst,set_field:00:00:00:00:00:01->dl_src,ovs-p1"])
+
+rm -rf p1.pcap
+NS_CHECK_EXEC([at_ns1], [tcpdump -l -n -xx -U -i p1 > p1.pcap &])
+sleep 1
+
+dnl The hex dump is a icmp packet. pkt=eth/ip/icmp
+dnl The packet is sent from p0(at_ns0) interface directed to
+dnl p1(at_ns1) interface
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 36 b1 ee 7c 01 02 36 b1 ee 7c 01 03 08 00 45 00 00 54 03 44 40 00 40 01 21 61 0a 01 01 01 0a 01 01 02 08 00 ef ac 7c e4 00 03 5b 2c 1f 61 00 00 00 00 50 0b 02 00 00 00 00 00 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37  > /dev/null])
+
+dnl Check the expected mpls encapsulated packet on the egress interface
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0000:  *0000 *0000 *0002 *0000 *0000 *0001 *8848 *0000" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0010:  *2140 *36b1 *ee7c *0102 *36b1 *ee7c *0103 *0800" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0020:  *4500 *0054 *0344 *4000 *4001 *2161 *0a01 *0101" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0030:  *0a01 *0102 *0800 *efac *7ce4 *0003 *5b2c *1f61" 2>&1 1>/dev/null])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([mpls_mc - encap header slow-path])
+AT_SKIP_IF([test $HAVE_TCPDUMP = no])
+OVS_TRAFFIC_VSWITCHD_START()
+
+AT_CHECK(ovs-appctl dpif/set-dp-features br0 add_mpls false)
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24", 36:b1:ee:7c:01:03)
+ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24", 36:b1:ee:7c:01:02)
+
+dnl The flow will encap a mpls header to the ip packet
+dnl eth/ip/icmp --> OVS --> eth/mpls/eth/ip/icmp
+AT_CHECK([ovs-ofctl -Oopenflow13 add-flow br0 "table=0,priority=100,dl_type=0x0800 actions=encap(mpls_mc),set_mpls_label:2,encap(ethernet),set_field:00:00:00:00:00:02->dl_dst,set_field:00:00:00:00:00:01->dl_src,ovs-p1"])
+
+rm -rf p1.pcap
+NS_CHECK_EXEC([at_ns1], [tcpdump -l -n -xx -U -i p1 > p1.pcap &])
+sleep 1
+
+dnl The hex dump is a icmp packet. pkt=eth/ip/icmp
+dnl The packet is sent from p0(at_ns0) interface directed to
+dnl p1(at_ns1) interface
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 36 b1 ee 7c 01 02 36 b1 ee 7c 01 03 08 00 45 00 00 54 03 44 40 00 40 01 21 61 0a 01 01 01 0a 01 01 02 08 00 ef ac 7c e4 00 03 5b 2c 1f 61 00 00 00 00 50 0b 02 00 00 00 00 00 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37  > /dev/null])
+
+dnl Check the expected mpls encapsulated packet on the egress interface
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0000:  *0000 *0000 *0002 *0000 *0000 *0001 *8848 *0000" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0010:  *2140 *36b1 *ee7c *0102 *36b1 *ee7c *0103 *0800" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0020:  *4500 *0054 *0344 *4000 *4001 *2161 *0a01 *0101" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0030:  *0a01 *0102 *0800 *efac *7ce4 *0003 *5b2c *1f61" 2>&1 1>/dev/null])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([mpls - decap header dp-support])
+AT_SKIP_IF([test $HAVE_TCPDUMP = no])
+OVS_TRAFFIC_VSWITCHD_START()
+
+AT_SKIP_IF([! ovs-appctl dpif/show-dp-features br0 2>&1 | grep "MPLS Label add: Yes" >/dev/null])
+
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24", 36:b1:ee:7c:01:03)
+ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24", 36:b1:ee:7c:01:02)
+
+dnl The flow will decap a mpls header which in turn carries a icmp packet
+dnl eth/mpls/eth/ip/icmp --> OVS --> eth/ip/icmp
+
+AT_CHECK([ovs-ofctl -Oopenflow13 add-flow br0 "table=0,priority=100,dl_type=0x8847,mpls_label=2 actions=decap(),decap(packet_type(ns=0,type=0)),ovs-p1"])
+
+rm -rf p1.pcap
+NS_CHECK_EXEC([at_ns1], [tcpdump -l -n -xx -U -i p1 > p1.pcap &])
+sleep 1
+
+dnl The hex dump is an mpls packet encapsulating ethernet packet. pkt=eth/mpls/eth/ip/icmp
+dnl The packet is sent from p0(at_ns0) interface directed to
+dnl p1(at_ns1) interface
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 00 00 00 00 00 02 00 00 00 00 00 01 88 47 00 00 21 40 36 b1 ee 7c 01 02 36 b1 ee 7c 01 03 08 00 45 00 00 54 03 44 40 00 40 01 21 61 0a 01 01 01 0a 01 01 02 08 00 ef ac 7c e4 00 03 5b 2c 1f 61 00 00 00 00 50 0b 02 00 00 00 00 00 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37  > /dev/null])
+
+dnl Check the expected decapsulated on the egress interface
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0000:  *36b1 *ee7c *0102 *36b1 *ee7c *0103 *0800 *4500" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0010:  *0054 *0344 *4000 *4001 *2161 *0a01 *0101 *0a01" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0020:  *0102 *0800 *efac *7ce4 *0003 *5b2c *1f61 *0000" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0030:  *0000 *500b *0200 *0000 *0000 *1011 *1213 *1415" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0040:  *1617 *1819 *1a1b *1c1d *1e1f *2021 *2223 *2425" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0050:  *2627 *2829 *2a2b *2c2d *2e2f *3031 *3233 *3435" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0060:  *3637" 2>&1 1>/dev/null])
+
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([mpls - decap header slow-path])
+AT_SKIP_IF([test $HAVE_TCPDUMP = no])
+OVS_TRAFFIC_VSWITCHD_START()
+
+AT_CHECK(ovs-appctl dpif/set-dp-features br0 add_mpls false)
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24", 36:b1:ee:7c:01:03)
+ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24", 36:b1:ee:7c:01:02)
+
+dnl The flow will decap a mpls header which in turn carries a icmp packet
+dnl eth/mpls/eth/ip/icmp --> OVS --> eth/ip/icmp
+
+AT_CHECK([ovs-ofctl -Oopenflow13 add-flow br0 "table=0,priority=100,dl_type=0x8847,mpls_label=2 actions=decap(),decap(packet_type(ns=0,type=0)),ovs-p1"])
+
+rm -rf p1.pcap
+NS_CHECK_EXEC([at_ns1], [tcpdump -l -n -xx -U -i p1 > p1.pcap &])
+sleep 1
+
+dnl The hex dump is an mpls packet encapsulating ethernet packet. pkt=eth/mpls/eth/ip/icmp
+dnl The packet is sent from p0(at_ns0) interface directed to
+dnl p1(at_ns1) interface
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 00 00 00 00 00 02 00 00 00 00 00 01 88 47 00 00 21 40 36 b1 ee 7c 01 02 36 b1 ee 7c 01 03 08 00 45 00 00 54 03 44 40 00 40 01 21 61 0a 01 01 01 0a 01 01 02 08 00 ef ac 7c e4 00 03 5b 2c 1f 61 00 00 00 00 50 0b 02 00 00 00 00 00 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37  > /dev/null])
+
+dnl Check the expected decapsulated on the egress interface
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0000:  *36b1 *ee7c *0102 *36b1 *ee7c *0103 *0800 *4500" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0010:  *0054 *0344 *4000 *4001 *2161 *0a01 *0101 *0a01" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0020:  *0102 *0800 *efac *7ce4 *0003 *5b2c *1f61 *0000" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0030:  *0000 *500b *0200 *0000 *0000 *1011 *1213 *1415" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0040:  *1617 *1819 *1a1b *1c1d *1e1f *2021 *2223 *2425" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0050:  *2627 *2829 *2a2b *2c2d *2e2f *3031 *3233 *3435" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p1.pcap | egrep "0x0060:  *3637" 2>&1 1>/dev/null])
+
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
+
+AT_SETUP([datapath - Encap Decap mpls actions])
+OVS_TRAFFIC_VSWITCHD_START([_ADD_BR([br1])])
+
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24")
+ADD_VETH(p1, at_ns1, br1, "10.1.1.2/24")
+
+AT_CHECK([ip link add patch0 type veth peer name patch1])
+on_exit 'ip link del patch0'
+
+AT_CHECK([ip link set dev patch0 up])
+AT_CHECK([ip link set dev patch1 up])
+AT_CHECK([ovs-vsctl add-port br0 patch0 -- set interface patch0 ofport_request=100])
+AT_CHECK([ovs-vsctl add-port br1 patch1 -- set interface patch1 ofport_request=100])
+
+AT_DATA([flows.txt], [dnl
+table=0,priority=100,in_port=ovs-p0 actions=encap(mpls),set_mpls_label:2,encap(ethernet),set_field:00:00:00:00:00:02->dl_dst,set_field:00:00:00:00:00:01->dl_src,output:100
+table=0,priority=100,in_port=100,dl_type=0x8847,mpls_label=2 actions=decap(),decap(packet_type(ns=0,type=0)),ovs-p0
+])
+
+AT_DATA([flows1.txt], [dnl
+table=0,priority=100,in_port=ovs-p1 actions=encap(mpls),set_mpls_label:2,encap(ethernet),set_field:00:00:00:00:00:02->dl_dst,set_field:00:00:00:00:00:01->dl_src,output:100
+table=0,priority=100,in_port=100,dl_type=0x8847,mpls_label=2 actions=decap(),decap(packet_type(ns=0,type=0)),ovs-p1
+])
+
+AT_CHECK([ovs-ofctl  -Oopenflow13 add-flows br0 flows.txt])
+AT_CHECK([ovs-ofctl  -Oopenflow13 add-flows br1 flows1.txt])
+
+NS_CHECK_EXEC([at_ns0], [ping -q -c 3 -i 0.3  10.1.1.2 | FORMAT_PING], [0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+
+NS_CHECK_EXEC([at_ns1], [ping -q -c 3 -i 0.3  10.1.1.1 | FORMAT_PING], [0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([datapath - multiple encap decap mpls actions])
+OVS_TRAFFIC_VSWITCHD_START([_ADD_BR([br1])])
+
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24")
+ADD_VETH(p1, at_ns1, br1, "10.1.1.2/24")
+
+AT_CHECK([ip link add patch0 type veth peer name patch1])
+on_exit 'ip link del patch0'
+
+AT_CHECK([ip link set dev patch0 up])
+AT_CHECK([ip link set dev patch1 up])
+AT_CHECK([ovs-vsctl add-port br0 patch0 -- set interface patch0 ofport_request=100])
+AT_CHECK([ovs-vsctl add-port br1 patch1 -- set interface patch1 ofport_request=100])
+
+AT_DATA([flows.txt], [dnl
+table=0,priority=100,in_port=ovs-p0 actions=encap(mpls),set_mpls_label:3, encap(mpls),set_mpls_label:2,encap(ethernet),set_field:00:00:00:00:00:02->dl_dst,set_field:00:00:00:00:00:01->dl_src,output:100
+table=0,priority=100,in_port=100,dl_type=0x8847,mpls_label=2 actions=decap(),decap(packet_type(ns=1,type=0x8847)),decap(packet_type(ns=0,type=0)),ovs-p0
+])
+
+AT_DATA([flows1.txt], [dnl
+table=0,priority=100,in_port=ovs-p1 actions=encap(mpls),set_mpls_label:3, encap(mpls),set_mpls_label:2,encap(ethernet),set_field:00:00:00:00:00:02->dl_dst,set_field:00:00:00:00:00:01->dl_src,output:100
+table=0,priority=100,in_port=100,dl_type=0x8847,mpls_label=2 actions=decap(),decap(packet_type(ns=1,type=0x8847)),decap(packet_type(ns=0,type=0)),ovs-p1
+])
+
+AT_CHECK([ovs-ofctl  -Oopenflow13 add-flows br0 flows.txt])
+AT_CHECK([ovs-ofctl  -Oopenflow13 add-flows br1 flows1.txt])
+
+NS_CHECK_EXEC([at_ns0], [ping -q -c 3 -i 0.3 10.1.1.2 | FORMAT_PING], [0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+
+NS_CHECK_EXEC([at_ns1], [ping -q -c 3 -i 0.3 10.1.1.1 | FORMAT_PING], [0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([datapath - encap mpls pop mpls actions])
+OVS_TRAFFIC_VSWITCHD_START([_ADD_BR([br1])])
+
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24", 36:b1:ee:7c:01:03)
+ADD_VETH(p1, at_ns1, br1, "10.1.1.2/24", 36:b1:ee:7c:01:02)
+
+AT_CHECK([ip link add patch0 type veth peer name patch1])
+on_exit 'ip link del patch0'
+
+AT_CHECK([ip link set dev patch0 up])
+AT_CHECK([ip link set dev patch1 up])
+AT_CHECK([ovs-vsctl add-port br0 patch0 -- set interface patch0 ofport_request=100])
+AT_CHECK([ovs-vsctl add-port br1 patch1 -- set interface patch1 ofport_request=100])
+
+AT_DATA([flows.txt], [dnl
+table=0,priority=100,dl_type=0x0800 actions=decap,encap(mpls),set_mpls_label:2,encap(ethernet),mod_dl_dst:36:b1:ee:7c:01:02,mod_dl_src:36:b1:ee:7c:01:03,output:100
+table=0,priority=100,dl_type=0x8847,mpls_label=2 actions=pop_mpls:0x0800,resubmit(,3)
+table=0,priority=10 actions=resubmit(,3)
+table=3,priority=10 actions=normal
+])
+
+AT_DATA([flows1.txt], [dnl
+table=0,priority=100,dl_type=0x0800 actions=decap,encap(mpls),set_mpls_label:2,encap(ethernet),mod_dl_dst:36:b1:ee:7c:01:03,mod_dl_src:36:b1:ee:7c:01:02,output:100
+table=0,priority=100,dl_type=0x8847,mpls_label=2 actions=pop_mpls:0x0800,resubmit(,3)
+table=0,priority=10 actions=resubmit(,3)
+table=3,priority=10 actions=normal
+])
+
+AT_CHECK([ovs-ofctl  -Oopenflow13 add-flows br0 flows.txt])
+AT_CHECK([ovs-ofctl  -Oopenflow13 add-flows br1 flows1.txt])
+
+NS_CHECK_EXEC([at_ns0], [ping -q -c 3 -i 0.3 10.1.1.2 | FORMAT_PING], [0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+NS_CHECK_EXEC([at_ns1], [ping -q -c 3 -i 0.3 10.1.1.1 | FORMAT_PING], [0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([datapath - push mpls decap mpls actions])
+OVS_TRAFFIC_VSWITCHD_START([_ADD_BR([br1])])
+
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24", 36:b1:ee:7c:01:03)
+ADD_VETH(p1, at_ns1, br1, "10.1.1.2/24", 36:b1:ee:7c:01:02)
+
+AT_CHECK([ip link add patch0 type veth peer name patch1])
+on_exit 'ip link del patch0'
+
+AT_CHECK([ip link set dev patch0 up])
+AT_CHECK([ip link set dev patch1 up])
+AT_CHECK([ovs-vsctl add-port br0 patch0 -- set interface patch0 ofport_request=100])
+AT_CHECK([ovs-vsctl add-port br1 patch1 -- set interface patch1 ofport_request=100])
+
+AT_DATA([flows.txt], [dnl
+table=0,priority=100,dl_type=0x0800 actions=push_mpls:0x8847,set_field:2->mpls_label,output:100
+table=0,priority=100,dl_type=0x8847,mpls_label=2 actions=decap,decap(packet_type(ns=1,type=0x0800)),encap(ethernet),mod_dl_dst:36:b1:ee:7c:01:03,mod_dl_src:36:b1:ee:7c:01:02,resubmit(,3)
+table=0,priority=10 actions=resubmit(,3)
+table=3,priority=10 actions=normal
+])
+
+AT_DATA([flows1.txt], [dnl
+table=0,priority=100,dl_type=0x0800 actions=push_mpls:0x8847,set_field:2->mpls_label,output:100
+table=0,priority=100,dl_type=0x8847,mpls_label=2 actions=decap,decap(packet_type(ns=1,type=0x0800)),encap(ethernet),mod_dl_dst:36:b1:ee:7c:01:02,mod_dl_src:36:b1:ee:7c:01:03,resubmit(,3)
+table=0,priority=10 actions=resubmit(,3)
+table=3,priority=10 actions=normal
+])
+
+AT_CHECK([ovs-ofctl  -Oopenflow13 add-flows br0 flows.txt])
+AT_CHECK([ovs-ofctl  -Oopenflow13 add-flows br1 flows1.txt])
+
+NS_CHECK_EXEC([at_ns0], [ping -q -c 3 -i 0.3 10.1.1.2 | FORMAT_PING], [0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+NS_CHECK_EXEC([at_ns1], [ping -q -c 3 -i 0.3 10.1.1.1 | FORMAT_PING], [0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
 AT_SETUP([datapath - basic truncate action])
 AT_SKIP_IF([test $HAVE_NC = no])
 OVS_TRAFFIC_VSWITCHD_START()