diff mbox series

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

Message ID 20211012042349.2564-1-martinvarghesenokia@gmail.com
State Changes Requested
Headers show
Series [ovs-dev,v7] 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 Oct. 12, 2021, 4:23 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>
---
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.

 Documentation/ref/ovs-actions.7.rst           |  17 +-
 NEWS                                          |   2 +-
 .../linux/compat/include/linux/openvswitch.h  |  33 +-
 include/openvswitch/ofp-ed-props.h            |  18 +
 lib/dpif-netdev.c                             |   1 +
 lib/dpif.c                                    |   1 +
 lib/odp-execute.c                             |  12 +
 lib/odp-util.c                                |  73 ++++-
 lib/ofp-actions.c                             |   5 +
 lib/ofp-ed-props.c                            |  91 ++++++
 lib/packets.c                                 |  45 +++
 lib/packets.h                                 |   2 +
 ofproto/ofproto-dpif-ipfix.c                  |   1 +
 ofproto/ofproto-dpif-sflow.c                  |   1 +
 ofproto/ofproto-dpif-xlate.c                  |  78 +++++
 ofproto/ofproto-dpif.c                        |  38 +++
 ofproto/ofproto-dpif.h                        |   4 +-
 tests/mpls-xlate.at                           |  57 ++++
 tests/odp.at                                  |   1 +
 tests/system-traffic.at                       | 308 ++++++++++++++++++
 20 files changed, 771 insertions(+), 17 deletions(-)

Comments

0-day Robot Oct. 12, 2021, 4: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 lacks whitespace around operator
WARNING: Line lacks whitespace around operator
#340 FILE: lib/odp-util.c:2616:
             eth_type=0x%"SCNx16")%n", &lse, &tc, &ttl, &bos, &eth_type, &n)) {

Lines checked: 1290, Warnings: 2, Errors: 0


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

Thanks,
0-day Robot
Eelco Chaudron Oct. 15, 2021, 11:40 a.m. UTC | #2
On 12 Oct 2021, at 6:23, 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>

Thanks for following through!

Looking forward to see the follow on patch to also include the OVS_ACTION_ATTR_ADD_MPLS in TC, i.e. netdev_tc_flow_put().


Acked-by: Eelco Chaudron <echaudro@redhat.com>
Martin Varghese Oct. 19, 2021, 4:35 p.m. UTC | #3
On Fri, Oct 15, 2021 at 01:40:39PM +0200, Eelco Chaudron wrote:
> 
> 
> On 12 Oct 2021, at 6:23, 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>
> 
> Thanks for following through!
> 
> Looking forward to see the follow on patch to also include the OVS_ACTION_ATTR_ADD_MPLS in TC, i.e. netdev_tc_flow_put().
> 
> 
> Acked-by: Eelco Chaudron <echaudro@redhat.com>
> 

Thanks Eelco.
Martin Varghese Nov. 1, 2021, 4:20 p.m. UTC | #4
On Fri, Oct 15, 2021 at 01:40:39PM +0200, Eelco Chaudron wrote:
> 
> 
> On 12 Oct 2021, at 6:23, 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>
> 
> Thanks for following through!
> 
> Looking forward to see the follow on patch to also include the OVS_ACTION_ATTR_ADD_MPLS in TC, i.e. netdev_tc_flow_put().
> 
> 
> Acked-by: Eelco Chaudron <echaudro@redhat.com>
> 
Hi Ilya,

If no further comments from anyone can we take this patch forward.

Regards,
Martin
Ilya Maximets Nov. 5, 2021, 2:05 p.m. UTC | #5
On 10/12/21 06:23, Martin Varghese wrote:
> From: Martin Varghese <martin.varghese@nokia.com>

Hi, Martin.  Thanks for working on this!

I didn't review most of the logic of the action itself, but see some
comments inline.

Bets regards, Ilya Maximets.

> 
> 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.

From the documentation it's really hard to understand the difference
between PUSH/POP_MPLS and encap/decap(mpls).  Could you please, add
notes about the difference to both: description of PUSH/POP_MPLS
and encap/decap(mpls) in the ovs-actions.7.rst ?

> 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>
> ---
> 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.
> 
>  Documentation/ref/ovs-actions.7.rst           |  17 +-
>  NEWS                                          |   2 +-
>  .../linux/compat/include/linux/openvswitch.h  |  33 +-
>  include/openvswitch/ofp-ed-props.h            |  18 +
>  lib/dpif-netdev.c                             |   1 +
>  lib/dpif.c                                    |   1 +
>  lib/odp-execute.c                             |  12 +
>  lib/odp-util.c                                |  73 ++++-
>  lib/ofp-actions.c                             |   5 +
>  lib/ofp-ed-props.c                            |  91 ++++++
>  lib/packets.c                                 |  45 +++
>  lib/packets.h                                 |   2 +
>  ofproto/ofproto-dpif-ipfix.c                  |   1 +
>  ofproto/ofproto-dpif-sflow.c                  |   1 +
>  ofproto/ofproto-dpif-xlate.c                  |  78 +++++
>  ofproto/ofproto-dpif.c                        |  38 +++
>  ofproto/ofproto-dpif.h                        |   4 +-
>  tests/mpls-xlate.at                           |  57 ++++
>  tests/odp.at                                  |   1 +
>  tests/system-traffic.at                       | 308 ++++++++++++++++++
>  20 files changed, 771 insertions(+), 17 deletions(-)
> 
> diff --git a/Documentation/ref/ovs-actions.7.rst b/Documentation/ref/ovs-actions.7.rst
> index 7224896df..9d60bf631 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.

Please, put two spaces between sentences like it's done in the rest of the document.

>    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,7 @@ The ``encap`` action
>    | ``encap(nsh([md_type=``\ *md_type*\
>                   ``], [tlv(``\ *class*,\ *type*,\ *value*\ ``)]...))``
>    | ``encap(ethernet)``
> +  | ``encap(mpls(ether_type=``\ *ether_type*\)

The last ')' should also be highlighted with ``.

>  
>  The ``encap`` action encapsulates a packet with a specified header.  It has
>  variants for different kinds of encapsulation.
> @@ -1018,6 +1020,10 @@ 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(ethertype=....))`` variant encapsulates an ethernet or

Hmm.  So, is it 'ether_type' or 'ethertype' ?

> +L3 packet with a MPLS header. The ethertype could be MPLS unicast (0x8847) or
> +multicast (0x8848) ethertypes.

Please, highlight the hex numbers.

> +
>  **Conformance**
>    This action is an Open vSwitch extension to OpenFlow 1.3 and later,
>    introduced in Open vSwitch 2.8.

This section, I think, should be updated in order to reflect in which OVS
version mpls variant is added.

> @@ -1030,6 +1036,7 @@ The ``decap`` action
>  
>  **Syntax**:
>    | ``decap``
> +  | ``decap([packet_type(ns=namespace,type=type)])``

Please, highlight the arguments.  And brackets, I guess,
not needed here.  It's either brackets or 2 syntax variants.

>  
>  Removes an outermost encapsulation from the packet:
>  
> @@ -1042,6 +1049,12 @@ Removes an outermost encapsulation from the packet:
>      and NSH inner packet types.  Other types raise unsupported packet type
>      errors.
>  
> +  - Otherwise, if the packet is a MPLS packet, 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`

And what if the inner packet is not the packet stated in the OF action?

> +
>    - Otherwise, raises an unsupported packet type error.
>  
>  **Conformance**

This section needs an update.

> diff --git a/NEWS b/NEWS
> index 90f4b1590..cac602d09 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -211,7 +211,7 @@ v2.14.0 - 17 Aug 2020
>     - GTP-U Tunnel Protocol
>       * Add two new fields: tun_gtpu_flags, tun_gtpu_msgtype.
>       * Only support for userspace datapath.
> -

Please, keep the line.

> +   - Encap & Decap action support for MPLS packet type.

Please, don't touch release notes for 2.14.  Move to Post-2.16.

>  
>  v2.13.0 - 14 Feb 2020
>  ---------------------
> diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h
> index f0595eeba..88a653771 100644
> --- a/datapath/linux/compat/include/linux/openvswitch.h
> +++ b/datapath/linux/compat/include/linux/openvswitch.h
> @@ -817,8 +817,32 @@ struct ovs_action_push_tnl {
>  };
>  #endif
>  
> -/**
> - * enum ovs_ct_attr - Attributes for %OVS_ACTION_ATTR_CT action.
> +/* 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.
> +						*/
> +

Above code is located in a different place in the kernel header.
Please move it accordingly.

> +/* enum ovs_ct_attr - Attributes for %OVS_ACTION_ATTR_CT action.
>   * @OVS_CT_ATTR_COMMIT: If present, commits the connection to the conntrack
>   * table. This allows future packets for the same connection to be identified
>   * as 'established' or 'related'. The flow key for the current packet will
> @@ -1008,6 +1032,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 +1065,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/include/openvswitch/ofp-ed-props.h b/include/openvswitch/ofp-ed-props.h
> index 306c6fe73..96ef3d951 100644
> --- a/include/openvswitch/ofp-ed-props.h
> +++ b/include/openvswitch/ofp-ed-props.h
> @@ -46,6 +46,11 @@ enum ofp_ed_nsh_prop_type {
>      OFPPPT_PROP_NSH_TLV = 2,     /* property TLV in NSH */
>  };
>  
> +enum ofp_ed_mpls_prop_type {
> +    OFPPPT_PROP_MPLS_NONE = 0,       /* unused */
> +    OFPPPT_PROP_MPLS_ETHERTYPE = 1,  /* MPLS Ethertype */
> +};
> +
>  /*
>   * External representation of encap/decap properties.
>   * These must be padded to a multiple of 8 bytes.
> @@ -72,6 +77,12 @@ struct ofp_ed_prop_nsh_tlv {
>      uint8_t data[0];
>  };
>  
> +struct ofp_ed_prop_mpls_ethertype {
> +    struct ofp_ed_prop_header header;
> +    uint16_t ether_type;     /* MPLS ethertype . */
> +    uint8_t pad[2];          /* Padding to 8 bytes. */
> +};
> +
>  /*
>   * Internal representation of encap/decap properties
>   */
> @@ -96,6 +107,13 @@ struct ofpact_ed_prop_nsh_tlv {
>      /* tlv_len octets of metadata value, padded to a multiple of 8 bytes. */
>      uint8_t data[0];
>  };
> +
> +struct ofpact_ed_prop_mpls_ethertype {
> +    struct ofpact_ed_prop header;
> +    uint16_t ether_type;     /* MPLS ethertype . */
> +    uint8_t pad[2];          /* Padding to 8 bytes. */
> +};
> +
>  enum ofperr decode_ed_prop(const struct ofp_ed_prop_header **ofp_prop,
>                             struct ofpbuf *out, size_t *remaining);
>  enum ofperr encode_ed_prop(const struct ofpact_ed_prop **prop,
> diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
> index 75f381ec1..140302a90 100644
> --- a/lib/dpif-netdev.c
> +++ b/lib/dpif-netdev.c
> @@ -8304,6 +8304,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 8c4aed47b..99aba3cae 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..709f4f31a 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)) {

Please, don't use a line continuation here.  Just close the '"' on the
first line and open a new one on the second. 

> +
> +            mpls.mpls_ethertype = htons(eth_type);
> +            mpls.mpls_lse = htonl(lse << MPLS_LABEL_SHIFT |
> +                                  tc << MPLS_TC_SHIFT |

Nit: Will look nicer with two spaces after 'tc'.

> +                                  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,10 @@ 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:
> +            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 +8669,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 +8718,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 +8726,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..8b74386b1 100644
> --- a/lib/ofp-actions.c
> +++ b/lib/ofp-actions.c
> @@ -4468,6 +4468,7 @@ 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:
>          /* Add supported encap header types here. */
>          break;
>      default:
> @@ -4519,6 +4520,8 @@ 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 {
>          return false;
>      }
> @@ -4600,6 +4603,8 @@ format_encap_pkt_type(const ovs_be32 pkt_type)
>          return "ethernet";
>      case PT_NSH:
>          return "nsh";
> +    case PT_MPLS:
> +        return "mpls";
>      default:
>          return "UNKNOWN";
>      }
> diff --git a/lib/ofp-ed-props.c b/lib/ofp-ed-props.c
> index 02a9235d5..14dfbae1e 100644
> --- a/lib/ofp-ed-props.c
> +++ b/lib/ofp-ed-props.c
> @@ -79,6 +79,27 @@ decode_ed_prop(const struct ofp_ed_prop_header **ofp_prop,
>          }
>          break;
>      }
> +    case OFPPPC_MPLS: {
> +        switch (prop_type) {
> +        case OFPPPT_PROP_MPLS_ETHERTYPE: {
> +            struct ofp_ed_prop_mpls_ethertype *opmet =
> +                ALIGNED_CAST(struct ofp_ed_prop_mpls_ethertype *, *ofp_prop);
> +            if (len > sizeof(*opmet) || len > *remaining) {

Please, don't parethesize the argument of a sizeof.  I know that
code in this file does that a lot, but it's not a reson to folow
a bad pattern.  Some for other places in this file.

No need to touch existing code, but the new code should follow
the coding style.

> +                return OFPERR_NXBAC_BAD_ED_PROP;
> +            }
> +            struct ofpact_ed_prop_mpls_ethertype *pmet =
> +                    ofpbuf_put_uninit(out, sizeof(*pmet));

This structure contains an uninitialized padding.  Later the
ofpact will be compared with memcmp causing incorect behavior.
Please, use ofpbuf_put_zero() here.  See the following commit
for an example of other places that needs to be fixed:

  51dec40f8e9d ("ofp-ed-props: Fix using uninitialized padding for NSH encap actions.")

Also, since you're adding a new OF action, please add a test
to tests/ofp-actions.at.  Again, see the example of such a test
in the commit above.

> +            pmet->header.prop_class = prop_class;
> +            pmet->header.type = prop_type;
> +            pmet->header.len = len;
> +            pmet->ether_type = opmet->ether_type;
> +            break;
> +        }
> +        default:
> +            return OFPERR_NXBAC_UNKNOWN_ED_PROP;
> +        }
> +        break;
> +    }
>      default:
>          return OFPERR_NXBAC_UNKNOWN_ED_PROP;
>      }
> @@ -134,6 +155,26 @@ encode_ed_prop(const struct ofpact_ed_prop **prop,
>          }
>          break;
>      }
> +    case OFPPPC_MPLS: {
> +        switch ((*prop)->type) {
> +        case OFPPPT_PROP_MPLS_ETHERTYPE: {
> +            struct ofpact_ed_prop_mpls_ethertype *pmet =
> +                ALIGNED_CAST(struct ofpact_ed_prop_mpls_ethertype *, *prop);
> +            struct ofp_ed_prop_mpls_ethertype *opmet =
> +                    ofpbuf_put_uninit(out, sizeof(*opmet));
> +            opmet->header.prop_class = htons((*prop)->prop_class);
> +            opmet->header.type = (*prop)->type;
> +            opmet->header.len =
> +                    offsetof(struct ofpact_ed_prop_mpls_ethertype, pad);
> +            opmet->ether_type = pmet->ether_type;
> +            prop_len = sizeof(*pmet);
> +            break;
> +        }
> +        default:
> +            return OFPERR_OFPBAC_BAD_ARGUMENT;
> +        }
> +        break;
> +    }
>      default:
>          return OFPERR_OFPBAC_BAD_ARGUMENT;
>      }
> @@ -181,6 +222,13 @@ parse_ed_prop_type(uint16_t prop_class,
>          } else {
>              return false;
>          }
> +    case OFPPPC_MPLS:
> +        if (!strcmp(str, "ether_type")) {
> +            *type = OFPPPT_PROP_MPLS_ETHERTYPE;
> +            return true;
> +        } else {
> +            return false;
> +        }
>      default:
>          return false;
>      }
> @@ -259,6 +307,29 @@ parse_ed_prop_value(uint16_t prop_class, uint8_t prop_type OVS_UNUSED,
>              OVS_NOT_REACHED();
>          }
>          break;
> +    case OFPPPC_MPLS:
> +        switch (prop_type) {
> +        case OFPPPT_PROP_MPLS_ETHERTYPE: {
> +            uint16_t ethertype;
> +            error = str_to_u16(value, "ether_type", &ethertype);
> +            if (error != NULL) {
> +                return error;
> +            }
> +            struct ofpact_ed_prop_mpls_ethertype *pmet =
> +                    ofpbuf_put_uninit(out, sizeof(*pmet));
> +            pmet->header.prop_class = prop_class;
> +            pmet->header.type = prop_type;
> +            pmet->header.len =
> +                    offsetof(struct ofpact_ed_prop_mpls_ethertype, pad);
> +            pmet->ether_type = ethertype;
> +
> +            break;
> +        }
> +        default:
> +            /* Unsupported property types rejected before. */
> +            OVS_NOT_REACHED();
> +        }
> +        break;
>      default:
>          /* Unsupported property classes rejected before. */
>          OVS_NOT_REACHED();
> @@ -300,6 +371,14 @@ format_ed_prop_type(const struct ofpact_ed_prop *prop)
>              OVS_NOT_REACHED();
>          }
>          break;
> +    case OFPPPC_MPLS:
> +        switch (prop->type) {
> +        case OFPPPT_PROP_MPLS_ETHERTYPE:
> +            return "ether_type";
> +        default:
> +            OVS_NOT_REACHED();
> +        }
> +        break;
>      default:
>          OVS_NOT_REACHED();
>      }
> @@ -332,6 +411,18 @@ format_ed_prop(struct ds *s OVS_UNUSED,
>          default:
>              OVS_NOT_REACHED();
>          }
> +    case OFPPPC_MPLS:
> +        switch (prop->type) {
> +        case OFPPPT_PROP_MPLS_ETHERTYPE: {
> +            struct ofpact_ed_prop_mpls_ethertype *pmet =
> +                ALIGNED_CAST(struct ofpact_ed_prop_mpls_ethertype *, prop);
> +            ds_put_format(s, "%s=%d", format_ed_prop_type(prop),
> +                          pmet->ether_type);
> +            return;
> +        }
> +        default:
> +            OVS_NOT_REACHED();
> +        }
>      default:
>          OVS_NOT_REACHED();
>      }
> diff --git a/lib/packets.c b/lib/packets.c
> index 4a7643c5d..d373e1580 100644
> --- a/lib/packets.c
> +++ b/lib/packets.c
> @@ -418,6 +418,39 @@ 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 = htonl(PT_MPLS);
> +    } 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 +473,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));

Nit: Please, shift above line one space to the left.

> +            }
> +        }
>      }
>  }
>  
> diff --git a/lib/packets.h b/lib/packets.h
> index 515bb59b1..f522492b1 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 8723cb4e8..0f68e281f 100644
> --- a/ofproto/ofproto-dpif-xlate.c
> +++ b/ofproto/ofproto-dpif-xlate.c
> @@ -6404,6 +6404,54 @@ 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)
> +{
> +    int n;
> +    uint32_t i;
> +    uint16_t ether_type;
> +    const char *ptr = (char *) encap->props;

Reverse xmas tree, please. :)

And is it necessary to cast to 'char *' here?

> +
> +    for (i = 0; i < encap->n_props; i++) {
> +        struct ofpact_ed_prop *prop_ptr =
> +            ALIGNED_CAST(struct ofpact_ed_prop *, ptr);
> +        if (prop_ptr->prop_class == OFPPPC_MPLS) {
> +            switch (prop_ptr->type) {
> +                case OFPPPT_PROP_MPLS_ETHERTYPE: {
> +                     struct ofpact_ed_prop_mpls_ethertype *prop_ether_type =
> +                        ALIGNED_CAST(struct ofpact_ed_prop_mpls_ethertype *,
> +                                     prop_ptr);
> +                    ether_type = prop_ether_type->ether_type;
> +                    break;
> +                }
> +            }
> +        }

You're not advancing the 'ptr' here checking the same data.
Please, add a unit test for this scenario.  It's not obvious,
how this should work.

> +    }
> +
> +    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 */

Period at the end of a comment.

> +    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 = htonl(PT_MPLS);
> +    n = flow_count_mpls_labels(flow, ctx->wc);
> +    flow_push_mpls(flow, n, htons(ether_type), ctx->wc, true);

I suppose, flow_push_mpls() will crash if  n > FLOW_MAX_MPLS_LABELS.
Should we check and stop processing in this case with
XLATE_TOO_MANY_MPLS_LABELS ?

Unit test for this would be great too.

> +    flow->dl_src = eth_addr_zero;
> +    flow->dl_dst = eth_addr_zero;
> +}
> +
>  /* 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
> @@ -6535,6 +6583,12 @@ 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:
> +            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();
> @@ -6606,6 +6660,30 @@ 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);
> +             }
> +             flow_pop_mpls(flow, n, ethertype, ctx->wc);

Repeating myself, but what happens if the inner packet actually has a
different packet type than one specified in the OF rule?

> +
> +             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;
> +             }
> +        }
>          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..eada8501c 100644
> --- a/tests/mpls-xlate.at
> +++ b/tests/mpls-xlate.at
> @@ -207,3 +207,60 @@ 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(ether_type=0x8847)),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
> 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/system-traffic.at b/tests/system-traffic.at
> index c4442c183..ee7120ed5 100644
> --- a/tests/system-traffic.at
> +++ b/tests/system-traffic.at
> @@ -1087,6 +1087,314 @@ 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])
> +OVS_TRAFFIC_VSWITCHD_START()
> +
> +AT_SKIP_IF([! ovs-appctl dpif/show-dp-features br0 2>&1 | grep "MPLS Label add: Yes" >/dev/null])

AT_SKIP_IF([test $HAVE_TCPDUMP = no])

Smae for other tests that use tcpdump.

> +
> +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(ether_type=0x8847)),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])
> +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(ether_type=0x8847)),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 - decap header dp-support])
> +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])
> +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(ether_type=0x8847)),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(ether_type=0x8847)),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(ether_type=0x8847)),set_mpls_label:3, encap(mpls(ether_type=0x8847)),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(ether_type=0x8847)),set_mpls_label:3, encap(mpls(ether_type=0x8847)),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(ether_type=0x8847)),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(ether_type=0x8847)),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()
>
Martin Varghese Nov. 10, 2021, 3:41 p.m. UTC | #6
On Fri, Nov 05, 2021 at 03:05:54PM +0100, Ilya Maximets wrote:
> On 10/12/21 06:23, Martin Varghese wrote:
> > From: Martin Varghese <martin.varghese@nokia.com>
> 
> Hi, Martin.  Thanks for working on this!
> 
> I didn't review most of the logic of the action itself, but see some
> comments inline.
> 
> Bets regards, Ilya Maximets.
>
Hi Ilya,

Thanks for your time.
I will work on your comments. Meanwhile please let me know the additional comments you have.

Please find my  inline responses for your comments.



> > 
> > 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.
> 
> From the documentation it's really hard to understand the difference
> between PUSH/POP_MPLS and encap/decap(mpls).  Could you please, add
> notes about the difference to both: description of PUSH/POP_MPLS
> and encap/decap(mpls) in the ovs-actions.7.rst ?
> 

I think the description  of encap/decap MPLS is little confusing and could be made better.
I think it is better to fix the description than referring ENCAP/DECAP in push_mpls/pop_mpls & vice versa.

I am planning to change like below

Encap

The ``encap(mpls(ethertype=....))`` adds an MPLS header at the start of the packet.

Decap

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.

Does this make sense?


> > 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>
> > ---
> > 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.
> > 
> >  Documentation/ref/ovs-actions.7.rst           |  17 +-
> >  NEWS                                          |   2 +-
> >  .../linux/compat/include/linux/openvswitch.h  |  33 +-
> >  include/openvswitch/ofp-ed-props.h            |  18 +
> >  lib/dpif-netdev.c                             |   1 +
> >  lib/dpif.c                                    |   1 +
> >  lib/odp-execute.c                             |  12 +
> >  lib/odp-util.c                                |  73 ++++-
> >  lib/ofp-actions.c                             |   5 +
> >  lib/ofp-ed-props.c                            |  91 ++++++
> >  lib/packets.c                                 |  45 +++
> >  lib/packets.h                                 |   2 +
> >  ofproto/ofproto-dpif-ipfix.c                  |   1 +
> >  ofproto/ofproto-dpif-sflow.c                  |   1 +
> >  ofproto/ofproto-dpif-xlate.c                  |  78 +++++
> >  ofproto/ofproto-dpif.c                        |  38 +++
> >  ofproto/ofproto-dpif.h                        |   4 +-
> >  tests/mpls-xlate.at                           |  57 ++++
> >  tests/odp.at                                  |   1 +
> >  tests/system-traffic.at                       | 308 ++++++++++++++++++
> >  20 files changed, 771 insertions(+), 17 deletions(-)
> > 
> > diff --git a/Documentation/ref/ovs-actions.7.rst b/Documentation/ref/ovs-actions.7.rst
> > index 7224896df..9d60bf631 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.
> 
> Please, put two spaces between sentences like it's done in the rest of the document.
> 
> >    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,7 @@ The ``encap`` action
> >    | ``encap(nsh([md_type=``\ *md_type*\
> >                   ``], [tlv(``\ *class*,\ *type*,\ *value*\ ``)]...))``
> >    | ``encap(ethernet)``
> > +  | ``encap(mpls(ether_type=``\ *ether_type*\)
> 
> The last ')' should also be highlighted with ``.
> 
> >  
> >  The ``encap`` action encapsulates a packet with a specified header.  It has
> >  variants for different kinds of encapsulation.
> > @@ -1018,6 +1020,10 @@ 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(ethertype=....))`` variant encapsulates an ethernet or
> 
> Hmm.  So, is it 'ether_type' or 'ethertype' ?
> 
> > +L3 packet with a MPLS header. The ethertype could be MPLS unicast (0x8847) or
> > +multicast (0x8848) ethertypes.
> 
> Please, highlight the hex numbers.
> 
> > +
> >  **Conformance**
> >    This action is an Open vSwitch extension to OpenFlow 1.3 and later,
> >    introduced in Open vSwitch 2.8.
> 
> This section, I think, should be updated in order to reflect in which OVS
> version mpls variant is added.
> 
> > @@ -1030,6 +1036,7 @@ The ``decap`` action
> >  
> >  **Syntax**:
> >    | ``decap``
> > +  | ``decap([packet_type(ns=namespace,type=type)])``
> 
> Please, highlight the arguments.  And brackets, I guess,
> not needed here.  It's either brackets or 2 syntax variants.

I assume you meant the American brackets 😊 . I will remove “[]” ?

> 
> >  
> >  Removes an outermost encapsulation from the packet:
> >  
> > @@ -1042,6 +1049,12 @@ Removes an outermost encapsulation from the packet:
> >      and NSH inner packet types.  Other types raise unsupported packet type
> >      errors.
> >  
> > +  - Otherwise, if the packet is a MPLS packet, 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`
> 
> And what if the inner packet is not the packet stated in the OF action?
>
It will wrongly classify the inner packet. But this problem should be there for pop_mpls also. MPLS header has no next protocol field.


> > +
> >    - Otherwise, raises an unsupported packet type error.
> >  
> >  **Conformance**
> 
> This section needs an update.
> 
> > diff --git a/NEWS b/NEWS
> > index 90f4b1590..cac602d09 100644
> > --- a/NEWS
> > +++ b/NEWS
> > @@ -211,7 +211,7 @@ v2.14.0 - 17 Aug 2020
> >     - GTP-U Tunnel Protocol
> >       * Add two new fields: tun_gtpu_flags, tun_gtpu_msgtype.
> >       * Only support for userspace datapath.
> > -
> 
> Please, keep the line.
> 
> > +   - Encap & Decap action support for MPLS packet type.
> 
> Please, don't touch release notes for 2.14.  Move to Post-2.16.
> 
> >  
> >  v2.13.0 - 14 Feb 2020
> >  ---------------------
> > diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h
> > index f0595eeba..88a653771 100644
> > --- a/datapath/linux/compat/include/linux/openvswitch.h
> > +++ b/datapath/linux/compat/include/linux/openvswitch.h
> > @@ -817,8 +817,32 @@ struct ovs_action_push_tnl {
> >  };
> >  #endif
> >  
> > -/**
> > - * enum ovs_ct_attr - Attributes for %OVS_ACTION_ATTR_CT action.
> > +/* 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.
> > +						*/
> > +
> 
> Above code is located in a different place in the kernel header.
> Please move it accordingly.
> 
> > +/* enum ovs_ct_attr - Attributes for %OVS_ACTION_ATTR_CT action.
> >   * @OVS_CT_ATTR_COMMIT: If present, commits the connection to the conntrack
> >   * table. This allows future packets for the same connection to be identified
> >   * as 'established' or 'related'. The flow key for the current packet will
> > @@ -1008,6 +1032,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 +1065,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/include/openvswitch/ofp-ed-props.h b/include/openvswitch/ofp-ed-props.h
> > index 306c6fe73..96ef3d951 100644
> > --- a/include/openvswitch/ofp-ed-props.h
> > +++ b/include/openvswitch/ofp-ed-props.h
> > @@ -46,6 +46,11 @@ enum ofp_ed_nsh_prop_type {
> >      OFPPPT_PROP_NSH_TLV = 2,     /* property TLV in NSH */
> >  };
> >  
> > +enum ofp_ed_mpls_prop_type {
> > +    OFPPPT_PROP_MPLS_NONE = 0,       /* unused */
> > +    OFPPPT_PROP_MPLS_ETHERTYPE = 1,  /* MPLS Ethertype */
> > +};
> > +
> >  /*
> >   * External representation of encap/decap properties.
> >   * These must be padded to a multiple of 8 bytes.
> > @@ -72,6 +77,12 @@ struct ofp_ed_prop_nsh_tlv {
> >      uint8_t data[0];
> >  };
> >  
> > +struct ofp_ed_prop_mpls_ethertype {
> > +    struct ofp_ed_prop_header header;
> > +    uint16_t ether_type;     /* MPLS ethertype . */
> > +    uint8_t pad[2];          /* Padding to 8 bytes. */
> > +};
> > +
> >  /*
> >   * Internal representation of encap/decap properties
> >   */
> > @@ -96,6 +107,13 @@ struct ofpact_ed_prop_nsh_tlv {
> >      /* tlv_len octets of metadata value, padded to a multiple of 8 bytes. */
> >      uint8_t data[0];
> >  };
> > +
> > +struct ofpact_ed_prop_mpls_ethertype {
> > +    struct ofpact_ed_prop header;
> > +    uint16_t ether_type;     /* MPLS ethertype . */
> > +    uint8_t pad[2];          /* Padding to 8 bytes. */
> > +};
> > +
> >  enum ofperr decode_ed_prop(const struct ofp_ed_prop_header **ofp_prop,
> >                             struct ofpbuf *out, size_t *remaining);
> >  enum ofperr encode_ed_prop(const struct ofpact_ed_prop **prop,
> > diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
> > index 75f381ec1..140302a90 100644
> > --- a/lib/dpif-netdev.c
> > +++ b/lib/dpif-netdev.c
> > @@ -8304,6 +8304,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 8c4aed47b..99aba3cae 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..709f4f31a 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)) {
> 
> Please, don't use a line continuation here.  Just close the '"' on the
> first line and open a new one on the second. 
> 
> > +
> > +            mpls.mpls_ethertype = htons(eth_type);
> > +            mpls.mpls_lse = htonl(lse << MPLS_LABEL_SHIFT |
> > +                                  tc << MPLS_TC_SHIFT |
> 
> Nit: Will look nicer with two spaces after 'tc'.
> 
> > +                                  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,10 @@ 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:
> > +            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 +8669,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 +8718,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 +8726,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..8b74386b1 100644
> > --- a/lib/ofp-actions.c
> > +++ b/lib/ofp-actions.c
> > @@ -4468,6 +4468,7 @@ 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:
> >          /* Add supported encap header types here. */
> >          break;
> >      default:
> > @@ -4519,6 +4520,8 @@ 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 {
> >          return false;
> >      }
> > @@ -4600,6 +4603,8 @@ format_encap_pkt_type(const ovs_be32 pkt_type)
> >          return "ethernet";
> >      case PT_NSH:
> >          return "nsh";
> > +    case PT_MPLS:
> > +        return "mpls";
> >      default:
> >          return "UNKNOWN";
> >      }
> > diff --git a/lib/ofp-ed-props.c b/lib/ofp-ed-props.c
> > index 02a9235d5..14dfbae1e 100644
> > --- a/lib/ofp-ed-props.c
> > +++ b/lib/ofp-ed-props.c
> > @@ -79,6 +79,27 @@ decode_ed_prop(const struct ofp_ed_prop_header **ofp_prop,
> >          }
> >          break;
> >      }
> > +    case OFPPPC_MPLS: {
> > +        switch (prop_type) {
> > +        case OFPPPT_PROP_MPLS_ETHERTYPE: {
> > +            struct ofp_ed_prop_mpls_ethertype *opmet =
> > +                ALIGNED_CAST(struct ofp_ed_prop_mpls_ethertype *, *ofp_prop);
> > +            if (len > sizeof(*opmet) || len > *remaining) {
> 
> Please, don't parethesize the argument of a sizeof.  I know that
> code in this file does that a lot, but it's not a reson to folow
> a bad pattern.  Some for other places in this file.
> 
> No need to touch existing code, but the new code should follow
> the coding style.
> 
> > +                return OFPERR_NXBAC_BAD_ED_PROP;
> > +            }
> > +            struct ofpact_ed_prop_mpls_ethertype *pmet =
> > +                    ofpbuf_put_uninit(out, sizeof(*pmet));
> 
> This structure contains an uninitialized padding.  Later the
> ofpact will be compared with memcmp causing incorect behavior.
> Please, use ofpbuf_put_zero() here.  See the following commit
> for an example of other places that needs to be fixed:
> 
>   51dec40f8e9d ("ofp-ed-props: Fix using uninitialized padding for NSH encap actions.")
> 
> Also, since you're adding a new OF action, please add a test
> to tests/ofp-actions.at.  Again, see the example of such a test
> in the commit above.
> 
> > +            pmet->header.prop_class = prop_class;
> > +            pmet->header.type = prop_type;
> > +            pmet->header.len = len;
> > +            pmet->ether_type = opmet->ether_type;
> > +            break;
> > +        }
> > +        default:
> > +            return OFPERR_NXBAC_UNKNOWN_ED_PROP;
> > +        }
> > +        break;
> > +    }
> >      default:
> >          return OFPERR_NXBAC_UNKNOWN_ED_PROP;
> >      }
> > @@ -134,6 +155,26 @@ encode_ed_prop(const struct ofpact_ed_prop **prop,
> >          }
> >          break;
> >      }
> > +    case OFPPPC_MPLS: {
> > +        switch ((*prop)->type) {
> > +        case OFPPPT_PROP_MPLS_ETHERTYPE: {
> > +            struct ofpact_ed_prop_mpls_ethertype *pmet =
> > +                ALIGNED_CAST(struct ofpact_ed_prop_mpls_ethertype *, *prop);
> > +            struct ofp_ed_prop_mpls_ethertype *opmet =
> > +                    ofpbuf_put_uninit(out, sizeof(*opmet));
> > +            opmet->header.prop_class = htons((*prop)->prop_class);
> > +            opmet->header.type = (*prop)->type;
> > +            opmet->header.len =
> > +                    offsetof(struct ofpact_ed_prop_mpls_ethertype, pad);
> > +            opmet->ether_type = pmet->ether_type;
> > +            prop_len = sizeof(*pmet);
> > +            break;
> > +        }
> > +        default:
> > +            return OFPERR_OFPBAC_BAD_ARGUMENT;
> > +        }
> > +        break;
> > +    }
> >      default:
> >          return OFPERR_OFPBAC_BAD_ARGUMENT;
> >      }
> > @@ -181,6 +222,13 @@ parse_ed_prop_type(uint16_t prop_class,
> >          } else {
> >              return false;
> >          }
> > +    case OFPPPC_MPLS:
> > +        if (!strcmp(str, "ether_type")) {
> > +            *type = OFPPPT_PROP_MPLS_ETHERTYPE;
> > +            return true;
> > +        } else {
> > +            return false;
> > +        }
> >      default:
> >          return false;
> >      }
> > @@ -259,6 +307,29 @@ parse_ed_prop_value(uint16_t prop_class, uint8_t prop_type OVS_UNUSED,
> >              OVS_NOT_REACHED();
> >          }
> >          break;
> > +    case OFPPPC_MPLS:
> > +        switch (prop_type) {
> > +        case OFPPPT_PROP_MPLS_ETHERTYPE: {
> > +            uint16_t ethertype;
> > +            error = str_to_u16(value, "ether_type", &ethertype);
> > +            if (error != NULL) {
> > +                return error;
> > +            }
> > +            struct ofpact_ed_prop_mpls_ethertype *pmet =
> > +                    ofpbuf_put_uninit(out, sizeof(*pmet));
> > +            pmet->header.prop_class = prop_class;
> > +            pmet->header.type = prop_type;
> > +            pmet->header.len =
> > +                    offsetof(struct ofpact_ed_prop_mpls_ethertype, pad);
> > +            pmet->ether_type = ethertype;
> > +
> > +            break;
> > +        }
> > +        default:
> > +            /* Unsupported property types rejected before. */
> > +            OVS_NOT_REACHED();
> > +        }
> > +        break;
> >      default:
> >          /* Unsupported property classes rejected before. */
> >          OVS_NOT_REACHED();
> > @@ -300,6 +371,14 @@ format_ed_prop_type(const struct ofpact_ed_prop *prop)
> >              OVS_NOT_REACHED();
> >          }
> >          break;
> > +    case OFPPPC_MPLS:
> > +        switch (prop->type) {
> > +        case OFPPPT_PROP_MPLS_ETHERTYPE:
> > +            return "ether_type";
> > +        default:
> > +            OVS_NOT_REACHED();
> > +        }
> > +        break;
> >      default:
> >          OVS_NOT_REACHED();
> >      }
> > @@ -332,6 +411,18 @@ format_ed_prop(struct ds *s OVS_UNUSED,
> >          default:
> >              OVS_NOT_REACHED();
> >          }
> > +    case OFPPPC_MPLS:
> > +        switch (prop->type) {
> > +        case OFPPPT_PROP_MPLS_ETHERTYPE: {
> > +            struct ofpact_ed_prop_mpls_ethertype *pmet =
> > +                ALIGNED_CAST(struct ofpact_ed_prop_mpls_ethertype *, prop);
> > +            ds_put_format(s, "%s=%d", format_ed_prop_type(prop),
> > +                          pmet->ether_type);
> > +            return;
> > +        }
> > +        default:
> > +            OVS_NOT_REACHED();
> > +        }
> >      default:
> >          OVS_NOT_REACHED();
> >      }
> > diff --git a/lib/packets.c b/lib/packets.c
> > index 4a7643c5d..d373e1580 100644
> > --- a/lib/packets.c
> > +++ b/lib/packets.c
> > @@ -418,6 +418,39 @@ 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 = htonl(PT_MPLS);
> > +    } 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 +473,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));
> 
> Nit: Please, shift above line one space to the left.
> 
> > +            }
> > +        }
> >      }
> >  }
> >  
> > diff --git a/lib/packets.h b/lib/packets.h
> > index 515bb59b1..f522492b1 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 8723cb4e8..0f68e281f 100644
> > --- a/ofproto/ofproto-dpif-xlate.c
> > +++ b/ofproto/ofproto-dpif-xlate.c
> > @@ -6404,6 +6404,54 @@ 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)
> > +{
> > +    int n;
> > +    uint32_t i;
> > +    uint16_t ether_type;
> > +    const char *ptr = (char *) encap->props;
> 
> Reverse xmas tree, please. :)
> 
> And is it necessary to cast to 'char *' here?
> 
> > +
> > +    for (i = 0; i < encap->n_props; i++) {
> > +        struct ofpact_ed_prop *prop_ptr =
> > +            ALIGNED_CAST(struct ofpact_ed_prop *, ptr);
> > +        if (prop_ptr->prop_class == OFPPPC_MPLS) {
> > +            switch (prop_ptr->type) {
> > +                case OFPPPT_PROP_MPLS_ETHERTYPE: {
> > +                     struct ofpact_ed_prop_mpls_ethertype *prop_ether_type =
> > +                        ALIGNED_CAST(struct ofpact_ed_prop_mpls_ethertype *,
> > +                                     prop_ptr);
> > +                    ether_type = prop_ether_type->ether_type;
> > +                    break;
> > +                }
> > +            }
> > +        }
> 
> You're not advancing the 'ptr' here checking the same data.
> Please, add a unit test for this scenario.  It's not obvious,
> how this should work.
> 
It will not work. I have tested only with n_props=1 and hence it worked for me.
I took the implementation from NSH and didn’t adapt it correctly. I will rework this part.

For MPLS, n_props always has to be 1. 
I will add a check for n_props=1 in parse_ENCAP for OFPPPC_MPLS prop_class.
I Will leave a error message “Invalid Argument” if n_prop!=1.


> > +    }
> > +
> > +    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 */
> 
> Period at the end of a comment.
> 
> > +    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 = htonl(PT_MPLS);
> > +    n = flow_count_mpls_labels(flow, ctx->wc);
> > +    flow_push_mpls(flow, n, htons(ether_type), ctx->wc, true);
> 
> I suppose, flow_push_mpls() will crash if  n > FLOW_MAX_MPLS_LABELS.
> Should we check and stop processing in this case with
> XLATE_TOO_MANY_MPLS_LABELS ?
> 
> Unit test for this would be great too.
> 
> > +    flow->dl_src = eth_addr_zero;
> > +    flow->dl_dst = eth_addr_zero;
> > +}
> > +
> >  /* 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
> > @@ -6535,6 +6583,12 @@ 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:
> > +            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();
> > @@ -6606,6 +6660,30 @@ 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);
> > +             }
> > +             flow_pop_mpls(flow, n, ethertype, ctx->wc);
> 
> Repeating myself, but what happens if the inner packet actually has a
> different packet type than one specified in the OF rule?
> 
> > +
> > +             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;
> > +             }
> > +        }
> >          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..eada8501c 100644
> > --- a/tests/mpls-xlate.at
> > +++ b/tests/mpls-xlate.at
> > @@ -207,3 +207,60 @@ 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(ether_type=0x8847)),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
> > 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/system-traffic.at b/tests/system-traffic.at
> > index c4442c183..ee7120ed5 100644
> > --- a/tests/system-traffic.at
> > +++ b/tests/system-traffic.at
> > @@ -1087,6 +1087,314 @@ 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])
> > +OVS_TRAFFIC_VSWITCHD_START()
> > +
> > +AT_SKIP_IF([! ovs-appctl dpif/show-dp-features br0 2>&1 | grep "MPLS Label add: Yes" >/dev/null])
> 
> AT_SKIP_IF([test $HAVE_TCPDUMP = no])
> 
> Smae for other tests that use tcpdump.
> 
> > +
> > +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(ether_type=0x8847)),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])
> > +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(ether_type=0x8847)),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 - decap header dp-support])
> > +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])
> > +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(ether_type=0x8847)),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(ether_type=0x8847)),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(ether_type=0x8847)),set_mpls_label:3, encap(mpls(ether_type=0x8847)),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(ether_type=0x8847)),set_mpls_label:3, encap(mpls(ether_type=0x8847)),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(ether_type=0x8847)),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(ether_type=0x8847)),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()
> > 
>
Martin Varghese Nov. 29, 2021, 7:27 a.m. UTC | #7
HI Ilya & Eelco,

I have sent the v8 of the patch.

In this version I have modified the encap action usage from
"encap(mpls(ethertype=0x884*)"   to  "encap(mpls) and encap(mpls_mc)".

MPLS unicast & MPLS multicast are 2  different packet_types.

PT_MPLS = PACKET_TYPE(OFPHTN_ETHERTYPE, ETH_TYPE_MPLS),
PT_MPLS_MC = PACKET_TYPE(OFPHTN_ETHERTYPE, ETH_TYPE_MPLS_MCAST)

Hence the new usage is more aligned with the encap action interface.
struct ofpact_encap {
    OFPACT_PADDED_MEMBERS(
        struct ofpact ofpact;
        ovs_be32 new_pkt_type;         /* Packet type of the header to add.
*/
        uint16_t hdr_size;             /* New header size in bytes. */
        uint16_t n_props;              /* Number of encap properties. */
    );
    struct ofpact_ed_prop props[]; /* Properties in internal format. */
};

With this change almost all the changes in ofp-ed-props.c are deleted and
very few lines are added to accommodate PT_MPLS_MC.

Please let me know your comments on v8.

Thanks again for your time.

Regards,
Martin




On Wed, Nov 10, 2021 at 9:11 PM Martin Varghese <
martinvarghesenokia@gmail.com> wrote:

> On Fri, Nov 05, 2021 at 03:05:54PM +0100, Ilya Maximets wrote:
> > On 10/12/21 06:23, Martin Varghese wrote:
> > > From: Martin Varghese <martin.varghese@nokia.com>
> >
> > Hi, Martin.  Thanks for working on this!
> >
> > I didn't review most of the logic of the action itself, but see some
> > comments inline.
> >
> > Bets regards, Ilya Maximets.
> >
> Hi Ilya,
>
> Thanks for your time.
> I will work on your comments. Meanwhile please let me know the additional
> comments you have.
>
> Please find my  inline responses for your comments.
>
>
>
> > >
> > > 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.
> >
> > From the documentation it's really hard to understand the difference
> > between PUSH/POP_MPLS and encap/decap(mpls).  Could you please, add
> > notes about the difference to both: description of PUSH/POP_MPLS
> > and encap/decap(mpls) in the ovs-actions.7.rst ?
> >
>
> I think the description  of encap/decap MPLS is little confusing and could
> be made better.
> I think it is better to fix the description than referring ENCAP/DECAP in
> push_mpls/pop_mpls & vice versa.
>
> I am planning to change like below
>
> Encap
>
> The ``encap(mpls(ethertype=....))`` adds an MPLS header at the start of
> the packet.
>
> Decap
>
> 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.
>
> Does this make sense?
>
>
> > > 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>
> > > ---
> > > 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.
> > >
> > >  Documentation/ref/ovs-actions.7.rst           |  17 +-
> > >  NEWS                                          |   2 +-
> > >  .../linux/compat/include/linux/openvswitch.h  |  33 +-
> > >  include/openvswitch/ofp-ed-props.h            |  18 +
> > >  lib/dpif-netdev.c                             |   1 +
> > >  lib/dpif.c                                    |   1 +
> > >  lib/odp-execute.c                             |  12 +
> > >  lib/odp-util.c                                |  73 ++++-
> > >  lib/ofp-actions.c                             |   5 +
> > >  lib/ofp-ed-props.c                            |  91 ++++++
> > >  lib/packets.c                                 |  45 +++
> > >  lib/packets.h                                 |   2 +
> > >  ofproto/ofproto-dpif-ipfix.c                  |   1 +
> > >  ofproto/ofproto-dpif-sflow.c                  |   1 +
> > >  ofproto/ofproto-dpif-xlate.c                  |  78 +++++
> > >  ofproto/ofproto-dpif.c                        |  38 +++
> > >  ofproto/ofproto-dpif.h                        |   4 +-
> > >  tests/mpls-xlate.at                           |  57 ++++
> > >  tests/odp.at                                  |   1 +
> > >  tests/system-traffic.at                       | 308
> ++++++++++++++++++
> > >  20 files changed, 771 insertions(+), 17 deletions(-)
> > >
> > > diff --git a/Documentation/ref/ovs-actions.7.rst
> b/Documentation/ref/ovs-actions.7.rst
> > > index 7224896df..9d60bf631 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.
> >
> > Please, put two spaces between sentences like it's done in the rest of
> the document.
> >
> > >    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,7 @@ The ``encap`` action
> > >    | ``encap(nsh([md_type=``\ *md_type*\
> > >                   ``], [tlv(``\ *class*,\ *type*,\ *value*\ ``)]...))``
> > >    | ``encap(ethernet)``
> > > +  | ``encap(mpls(ether_type=``\ *ether_type*\)
> >
> > The last ')' should also be highlighted with ``.
> >
> > >
> > >  The ``encap`` action encapsulates a packet with a specified header.
> It has
> > >  variants for different kinds of encapsulation.
> > > @@ -1018,6 +1020,10 @@ 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(ethertype=....))`` variant encapsulates an ethernet
> or
> >
> > Hmm.  So, is it 'ether_type' or 'ethertype' ?
> >
> > > +L3 packet with a MPLS header. The ethertype could be MPLS unicast
> (0x8847) or
> > > +multicast (0x8848) ethertypes.
> >
> > Please, highlight the hex numbers.
> >
> > > +
> > >  **Conformance**
> > >    This action is an Open vSwitch extension to OpenFlow 1.3 and later,
> > >    introduced in Open vSwitch 2.8.
> >
> > This section, I think, should be updated in order to reflect in which OVS
> > version mpls variant is added.
> >
> > > @@ -1030,6 +1036,7 @@ The ``decap`` action
> > >
> > >  **Syntax**:
> > >    | ``decap``
> > > +  | ``decap([packet_type(ns=namespace,type=type)])``
> >
> > Please, highlight the arguments.  And brackets, I guess,
> > not needed here.  It's either brackets or 2 syntax variants.
>
> I assume you meant the American brackets 😊 . I will remove “[]” ?
>
> >
> > >
> > >  Removes an outermost encapsulation from the packet:
> > >
> > > @@ -1042,6 +1049,12 @@ Removes an outermost encapsulation from the
> packet:
> > >      and NSH inner packet types.  Other types raise unsupported packet
> type
> > >      errors.
> > >
> > > +  - Otherwise, if the packet is a MPLS packet, 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`
> >
> > And what if the inner packet is not the packet stated in the OF action?
> >
> It will wrongly classify the inner packet. But this problem should be
> there for pop_mpls also. MPLS header has no next protocol field.
>
>
> > > +
> > >    - Otherwise, raises an unsupported packet type error.
> > >
> > >  **Conformance**
> >
> > This section needs an update.
> >
> > > diff --git a/NEWS b/NEWS
> > > index 90f4b1590..cac602d09 100644
> > > --- a/NEWS
> > > +++ b/NEWS
> > > @@ -211,7 +211,7 @@ v2.14.0 - 17 Aug 2020
> > >     - GTP-U Tunnel Protocol
> > >       * Add two new fields: tun_gtpu_flags, tun_gtpu_msgtype.
> > >       * Only support for userspace datapath.
> > > -
> >
> > Please, keep the line.
> >
> > > +   - Encap & Decap action support for MPLS packet type.
> >
> > Please, don't touch release notes for 2.14.  Move to Post-2.16.
> >
> > >
> > >  v2.13.0 - 14 Feb 2020
> > >  ---------------------
> > > diff --git a/datapath/linux/compat/include/linux/openvswitch.h
> b/datapath/linux/compat/include/linux/openvswitch.h
> > > index f0595eeba..88a653771 100644
> > > --- a/datapath/linux/compat/include/linux/openvswitch.h
> > > +++ b/datapath/linux/compat/include/linux/openvswitch.h
> > > @@ -817,8 +817,32 @@ struct ovs_action_push_tnl {
> > >  };
> > >  #endif
> > >
> > > -/**
> > > - * enum ovs_ct_attr - Attributes for %OVS_ACTION_ATTR_CT action.
> > > +/* 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.
> > > +                                           */
> > > +
> >
> > Above code is located in a different place in the kernel header.
> > Please move it accordingly.
> >
> > > +/* enum ovs_ct_attr - Attributes for %OVS_ACTION_ATTR_CT action.
> > >   * @OVS_CT_ATTR_COMMIT: If present, commits the connection to the
> conntrack
> > >   * table. This allows future packets for the same connection to be
> identified
> > >   * as 'established' or 'related'. The flow key for the current packet
> will
> > > @@ -1008,6 +1032,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 +1065,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/include/openvswitch/ofp-ed-props.h
> b/include/openvswitch/ofp-ed-props.h
> > > index 306c6fe73..96ef3d951 100644
> > > --- a/include/openvswitch/ofp-ed-props.h
> > > +++ b/include/openvswitch/ofp-ed-props.h
> > > @@ -46,6 +46,11 @@ enum ofp_ed_nsh_prop_type {
> > >      OFPPPT_PROP_NSH_TLV = 2,     /* property TLV in NSH */
> > >  };
> > >
> > > +enum ofp_ed_mpls_prop_type {
> > > +    OFPPPT_PROP_MPLS_NONE = 0,       /* unused */
> > > +    OFPPPT_PROP_MPLS_ETHERTYPE = 1,  /* MPLS Ethertype */
> > > +};
> > > +
> > >  /*
> > >   * External representation of encap/decap properties.
> > >   * These must be padded to a multiple of 8 bytes.
> > > @@ -72,6 +77,12 @@ struct ofp_ed_prop_nsh_tlv {
> > >      uint8_t data[0];
> > >  };
> > >
> > > +struct ofp_ed_prop_mpls_ethertype {
> > > +    struct ofp_ed_prop_header header;
> > > +    uint16_t ether_type;     /* MPLS ethertype . */
> > > +    uint8_t pad[2];          /* Padding to 8 bytes. */
> > > +};
> > > +
> > >  /*
> > >   * Internal representation of encap/decap properties
> > >   */
> > > @@ -96,6 +107,13 @@ struct ofpact_ed_prop_nsh_tlv {
> > >      /* tlv_len octets of metadata value, padded to a multiple of 8
> bytes. */
> > >      uint8_t data[0];
> > >  };
> > > +
> > > +struct ofpact_ed_prop_mpls_ethertype {
> > > +    struct ofpact_ed_prop header;
> > > +    uint16_t ether_type;     /* MPLS ethertype . */
> > > +    uint8_t pad[2];          /* Padding to 8 bytes. */
> > > +};
> > > +
> > >  enum ofperr decode_ed_prop(const struct ofp_ed_prop_header **ofp_prop,
> > >                             struct ofpbuf *out, size_t *remaining);
> > >  enum ofperr encode_ed_prop(const struct ofpact_ed_prop **prop,
> > > diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
> > > index 75f381ec1..140302a90 100644
> > > --- a/lib/dpif-netdev.c
> > > +++ b/lib/dpif-netdev.c
> > > @@ -8304,6 +8304,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 8c4aed47b..99aba3cae 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..709f4f31a 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)) {
> >
> > Please, don't use a line continuation here.  Just close the '"' on the
> > first line and open a new one on the second.
> >
> > > +
> > > +            mpls.mpls_ethertype = htons(eth_type);
> > > +            mpls.mpls_lse = htonl(lse << MPLS_LABEL_SHIFT |
> > > +                                  tc << MPLS_TC_SHIFT |
> >
> > Nit: Will look nicer with two spaces after 'tc'.
> >
> > > +                                  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,10 @@ 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:
> > > +            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 +8669,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 +8718,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 +8726,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..8b74386b1 100644
> > > --- a/lib/ofp-actions.c
> > > +++ b/lib/ofp-actions.c
> > > @@ -4468,6 +4468,7 @@ 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:
> > >          /* Add supported encap header types here. */
> > >          break;
> > >      default:
> > > @@ -4519,6 +4520,8 @@ 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 {
> > >          return false;
> > >      }
> > > @@ -4600,6 +4603,8 @@ format_encap_pkt_type(const ovs_be32 pkt_type)
> > >          return "ethernet";
> > >      case PT_NSH:
> > >          return "nsh";
> > > +    case PT_MPLS:
> > > +        return "mpls";
> > >      default:
> > >          return "UNKNOWN";
> > >      }
> > > diff --git a/lib/ofp-ed-props.c b/lib/ofp-ed-props.c
> > > index 02a9235d5..14dfbae1e 100644
> > > --- a/lib/ofp-ed-props.c
> > > +++ b/lib/ofp-ed-props.c
> > > @@ -79,6 +79,27 @@ decode_ed_prop(const struct ofp_ed_prop_header
> **ofp_prop,
> > >          }
> > >          break;
> > >      }
> > > +    case OFPPPC_MPLS: {
> > > +        switch (prop_type) {
> > > +        case OFPPPT_PROP_MPLS_ETHERTYPE: {
> > > +            struct ofp_ed_prop_mpls_ethertype *opmet =
> > > +                ALIGNED_CAST(struct ofp_ed_prop_mpls_ethertype *,
> *ofp_prop);
> > > +            if (len > sizeof(*opmet) || len > *remaining) {
> >
> > Please, don't parethesize the argument of a sizeof.  I know that
> > code in this file does that a lot, but it's not a reson to folow
> > a bad pattern.  Some for other places in this file.
> >
> > No need to touch existing code, but the new code should follow
> > the coding style.
> >
> > > +                return OFPERR_NXBAC_BAD_ED_PROP;
> > > +            }
> > > +            struct ofpact_ed_prop_mpls_ethertype *pmet =
> > > +                    ofpbuf_put_uninit(out, sizeof(*pmet));
> >
> > This structure contains an uninitialized padding.  Later the
> > ofpact will be compared with memcmp causing incorect behavior.
> > Please, use ofpbuf_put_zero() here.  See the following commit
> > for an example of other places that needs to be fixed:
> >
> >   51dec40f8e9d ("ofp-ed-props: Fix using uninitialized padding for NSH
> encap actions.")
> >
> > Also, since you're adding a new OF action, please add a test
> > to tests/ofp-actions.at.  Again, see the example of such a test
> > in the commit above.
> >
> > > +            pmet->header.prop_class = prop_class;
> > > +            pmet->header.type = prop_type;
> > > +            pmet->header.len = len;
> > > +            pmet->ether_type = opmet->ether_type;
> > > +            break;
> > > +        }
> > > +        default:
> > > +            return OFPERR_NXBAC_UNKNOWN_ED_PROP;
> > > +        }
> > > +        break;
> > > +    }
> > >      default:
> > >          return OFPERR_NXBAC_UNKNOWN_ED_PROP;
> > >      }
> > > @@ -134,6 +155,26 @@ encode_ed_prop(const struct ofpact_ed_prop **prop,
> > >          }
> > >          break;
> > >      }
> > > +    case OFPPPC_MPLS: {
> > > +        switch ((*prop)->type) {
> > > +        case OFPPPT_PROP_MPLS_ETHERTYPE: {
> > > +            struct ofpact_ed_prop_mpls_ethertype *pmet =
> > > +                ALIGNED_CAST(struct ofpact_ed_prop_mpls_ethertype *,
> *prop);
> > > +            struct ofp_ed_prop_mpls_ethertype *opmet =
> > > +                    ofpbuf_put_uninit(out, sizeof(*opmet));
> > > +            opmet->header.prop_class = htons((*prop)->prop_class);
> > > +            opmet->header.type = (*prop)->type;
> > > +            opmet->header.len =
> > > +                    offsetof(struct ofpact_ed_prop_mpls_ethertype,
> pad);
> > > +            opmet->ether_type = pmet->ether_type;
> > > +            prop_len = sizeof(*pmet);
> > > +            break;
> > > +        }
> > > +        default:
> > > +            return OFPERR_OFPBAC_BAD_ARGUMENT;
> > > +        }
> > > +        break;
> > > +    }
> > >      default:
> > >          return OFPERR_OFPBAC_BAD_ARGUMENT;
> > >      }
> > > @@ -181,6 +222,13 @@ parse_ed_prop_type(uint16_t prop_class,
> > >          } else {
> > >              return false;
> > >          }
> > > +    case OFPPPC_MPLS:
> > > +        if (!strcmp(str, "ether_type")) {
> > > +            *type = OFPPPT_PROP_MPLS_ETHERTYPE;
> > > +            return true;
> > > +        } else {
> > > +            return false;
> > > +        }
> > >      default:
> > >          return false;
> > >      }
> > > @@ -259,6 +307,29 @@ parse_ed_prop_value(uint16_t prop_class, uint8_t
> prop_type OVS_UNUSED,
> > >              OVS_NOT_REACHED();
> > >          }
> > >          break;
> > > +    case OFPPPC_MPLS:
> > > +        switch (prop_type) {
> > > +        case OFPPPT_PROP_MPLS_ETHERTYPE: {
> > > +            uint16_t ethertype;
> > > +            error = str_to_u16(value, "ether_type", &ethertype);
> > > +            if (error != NULL) {
> > > +                return error;
> > > +            }
> > > +            struct ofpact_ed_prop_mpls_ethertype *pmet =
> > > +                    ofpbuf_put_uninit(out, sizeof(*pmet));
> > > +            pmet->header.prop_class = prop_class;
> > > +            pmet->header.type = prop_type;
> > > +            pmet->header.len =
> > > +                    offsetof(struct ofpact_ed_prop_mpls_ethertype,
> pad);
> > > +            pmet->ether_type = ethertype;
> > > +
> > > +            break;
> > > +        }
> > > +        default:
> > > +            /* Unsupported property types rejected before. */
> > > +            OVS_NOT_REACHED();
> > > +        }
> > > +        break;
> > >      default:
> > >          /* Unsupported property classes rejected before. */
> > >          OVS_NOT_REACHED();
> > > @@ -300,6 +371,14 @@ format_ed_prop_type(const struct ofpact_ed_prop
> *prop)
> > >              OVS_NOT_REACHED();
> > >          }
> > >          break;
> > > +    case OFPPPC_MPLS:
> > > +        switch (prop->type) {
> > > +        case OFPPPT_PROP_MPLS_ETHERTYPE:
> > > +            return "ether_type";
> > > +        default:
> > > +            OVS_NOT_REACHED();
> > > +        }
> > > +        break;
> > >      default:
> > >          OVS_NOT_REACHED();
> > >      }
> > > @@ -332,6 +411,18 @@ format_ed_prop(struct ds *s OVS_UNUSED,
> > >          default:
> > >              OVS_NOT_REACHED();
> > >          }
> > > +    case OFPPPC_MPLS:
> > > +        switch (prop->type) {
> > > +        case OFPPPT_PROP_MPLS_ETHERTYPE: {
> > > +            struct ofpact_ed_prop_mpls_ethertype *pmet =
> > > +                ALIGNED_CAST(struct ofpact_ed_prop_mpls_ethertype *,
> prop);
> > > +            ds_put_format(s, "%s=%d", format_ed_prop_type(prop),
> > > +                          pmet->ether_type);
> > > +            return;
> > > +        }
> > > +        default:
> > > +            OVS_NOT_REACHED();
> > > +        }
> > >      default:
> > >          OVS_NOT_REACHED();
> > >      }
> > > diff --git a/lib/packets.c b/lib/packets.c
> > > index 4a7643c5d..d373e1580 100644
> > > --- a/lib/packets.c
> > > +++ b/lib/packets.c
> > > @@ -418,6 +418,39 @@ 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 = htonl(PT_MPLS);
> > > +    } 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 +473,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));
> >
> > Nit: Please, shift above line one space to the left.
> >
> > > +            }
> > > +        }
> > >      }
> > >  }
> > >
> > > diff --git a/lib/packets.h b/lib/packets.h
> > > index 515bb59b1..f522492b1 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 8723cb4e8..0f68e281f 100644
> > > --- a/ofproto/ofproto-dpif-xlate.c
> > > +++ b/ofproto/ofproto-dpif-xlate.c
> > > @@ -6404,6 +6404,54 @@ 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)
> > > +{
> > > +    int n;
> > > +    uint32_t i;
> > > +    uint16_t ether_type;
> > > +    const char *ptr = (char *) encap->props;
> >
> > Reverse xmas tree, please. :)
> >
> > And is it necessary to cast to 'char *' here?
> >
> > > +
> > > +    for (i = 0; i < encap->n_props; i++) {
> > > +        struct ofpact_ed_prop *prop_ptr =
> > > +            ALIGNED_CAST(struct ofpact_ed_prop *, ptr);
> > > +        if (prop_ptr->prop_class == OFPPPC_MPLS) {
> > > +            switch (prop_ptr->type) {
> > > +                case OFPPPT_PROP_MPLS_ETHERTYPE: {
> > > +                     struct ofpact_ed_prop_mpls_ethertype
> *prop_ether_type =
> > > +                        ALIGNED_CAST(struct
> ofpact_ed_prop_mpls_ethertype *,
> > > +                                     prop_ptr);
> > > +                    ether_type = prop_ether_type->ether_type;
> > > +                    break;
> > > +                }
> > > +            }
> > > +        }
> >
> > You're not advancing the 'ptr' here checking the same data.
> > Please, add a unit test for this scenario.  It's not obvious,
> > how this should work.
> >
> It will not work. I have tested only with n_props=1 and hence it worked
> for me.
> I took the implementation from NSH and didn’t adapt it correctly. I will
> rework this part.
>
> For MPLS, n_props always has to be 1.
> I will add a check for n_props=1 in parse_ENCAP for OFPPPC_MPLS prop_class.
> I Will leave a error message “Invalid Argument” if n_prop!=1.
>
>
> > > +    }
> > > +
> > > +    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 */
> >
> > Period at the end of a comment.
> >
> > > +    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 = htonl(PT_MPLS);
> > > +    n = flow_count_mpls_labels(flow, ctx->wc);
> > > +    flow_push_mpls(flow, n, htons(ether_type), ctx->wc, true);
> >
> > I suppose, flow_push_mpls() will crash if  n > FLOW_MAX_MPLS_LABELS.
> > Should we check and stop processing in this case with
> > XLATE_TOO_MANY_MPLS_LABELS ?
> >
> > Unit test for this would be great too.
> >
> > > +    flow->dl_src = eth_addr_zero;
> > > +    flow->dl_dst = eth_addr_zero;
> > > +}
> > > +
> > >  /* 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
> > > @@ -6535,6 +6583,12 @@ 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:
> > > +            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();
> > > @@ -6606,6 +6660,30 @@ 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);
> > > +             }
> > > +             flow_pop_mpls(flow, n, ethertype, ctx->wc);
> >
> > Repeating myself, but what happens if the inner packet actually has a
> > different packet type than one specified in the OF rule?
> >
> > > +
> > > +             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;
> > > +             }
> > > +        }
> > >          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..eada8501c 100644
> > > --- a/tests/mpls-xlate.at
> > > +++ b/tests/mpls-xlate.at
> > > @@ -207,3 +207,60 @@ 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(ether_type=0x8847)),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
> > > 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/system-traffic.at b/tests/system-traffic.at
> > > index c4442c183..ee7120ed5 100644
> > > --- a/tests/system-traffic.at
> > > +++ b/tests/system-traffic.at
> > > @@ -1087,6 +1087,314 @@ 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])
> > > +OVS_TRAFFIC_VSWITCHD_START()
> > > +
> > > +AT_SKIP_IF([! ovs-appctl dpif/show-dp-features br0 2>&1 | grep "MPLS
> Label add: Yes" >/dev/null])
> >
> > AT_SKIP_IF([test $HAVE_TCPDUMP = no])
> >
> > Smae for other tests that use tcpdump.
> >
> > > +
> > > +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(ether_type=0x8847)),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])
> > > +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(ether_type=0x8847)),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 - decap header dp-support])
> > > +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])
> > > +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(ether_type=0x8847)),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(ether_type=0x8847)),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(ether_type=0x8847)),set_mpls_label:3,
> encap(mpls(ether_type=0x8847)),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(ether_type=0x8847)),set_mpls_label:3,
> encap(mpls(ether_type=0x8847)),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(ether_type=0x8847)),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(ether_type=0x8847)),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()
> > >
> >
>
diff mbox series

Patch

diff --git a/Documentation/ref/ovs-actions.7.rst b/Documentation/ref/ovs-actions.7.rst
index 7224896df..9d60bf631 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,7 @@  The ``encap`` action
   | ``encap(nsh([md_type=``\ *md_type*\
                  ``], [tlv(``\ *class*,\ *type*,\ *value*\ ``)]...))``
   | ``encap(ethernet)``
+  | ``encap(mpls(ether_type=``\ *ether_type*\)
 
 The ``encap`` action encapsulates a packet with a specified header.  It has
 variants for different kinds of encapsulation.
@@ -1018,6 +1020,10 @@  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(ethertype=....))`` variant encapsulates an ethernet or
+L3 packet with a MPLS header. The ethertype could be MPLS unicast (0x8847) or
+multicast (0x8848) ethertypes.
+
 **Conformance**
   This action is an Open vSwitch extension to OpenFlow 1.3 and later,
   introduced in Open vSwitch 2.8.
@@ -1030,6 +1036,7 @@  The ``decap`` action
 
 **Syntax**:
   | ``decap``
+  | ``decap([packet_type(ns=namespace,type=type)])``
 
 Removes an outermost encapsulation from the packet:
 
@@ -1042,6 +1049,12 @@  Removes an outermost encapsulation from the packet:
     and NSH inner packet types.  Other types raise unsupported packet type
     errors.
 
+  - Otherwise, if the packet is a MPLS packet, 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`
+
   - Otherwise, raises an unsupported packet type error.
 
 **Conformance**
diff --git a/NEWS b/NEWS
index 90f4b1590..cac602d09 100644
--- a/NEWS
+++ b/NEWS
@@ -211,7 +211,7 @@  v2.14.0 - 17 Aug 2020
    - GTP-U Tunnel Protocol
      * Add two new fields: tun_gtpu_flags, tun_gtpu_msgtype.
      * Only support for userspace datapath.
-
+   - Encap & Decap action support for MPLS packet type.
 
 v2.13.0 - 14 Feb 2020
 ---------------------
diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h
index f0595eeba..88a653771 100644
--- a/datapath/linux/compat/include/linux/openvswitch.h
+++ b/datapath/linux/compat/include/linux/openvswitch.h
@@ -817,8 +817,32 @@  struct ovs_action_push_tnl {
 };
 #endif
 
-/**
- * enum ovs_ct_attr - Attributes for %OVS_ACTION_ATTR_CT action.
+/* 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.
+						*/
+
+/* enum ovs_ct_attr - Attributes for %OVS_ACTION_ATTR_CT action.
  * @OVS_CT_ATTR_COMMIT: If present, commits the connection to the conntrack
  * table. This allows future packets for the same connection to be identified
  * as 'established' or 'related'. The flow key for the current packet will
@@ -1008,6 +1032,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 +1065,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/include/openvswitch/ofp-ed-props.h b/include/openvswitch/ofp-ed-props.h
index 306c6fe73..96ef3d951 100644
--- a/include/openvswitch/ofp-ed-props.h
+++ b/include/openvswitch/ofp-ed-props.h
@@ -46,6 +46,11 @@  enum ofp_ed_nsh_prop_type {
     OFPPPT_PROP_NSH_TLV = 2,     /* property TLV in NSH */
 };
 
+enum ofp_ed_mpls_prop_type {
+    OFPPPT_PROP_MPLS_NONE = 0,       /* unused */
+    OFPPPT_PROP_MPLS_ETHERTYPE = 1,  /* MPLS Ethertype */
+};
+
 /*
  * External representation of encap/decap properties.
  * These must be padded to a multiple of 8 bytes.
@@ -72,6 +77,12 @@  struct ofp_ed_prop_nsh_tlv {
     uint8_t data[0];
 };
 
+struct ofp_ed_prop_mpls_ethertype {
+    struct ofp_ed_prop_header header;
+    uint16_t ether_type;     /* MPLS ethertype . */
+    uint8_t pad[2];          /* Padding to 8 bytes. */
+};
+
 /*
  * Internal representation of encap/decap properties
  */
@@ -96,6 +107,13 @@  struct ofpact_ed_prop_nsh_tlv {
     /* tlv_len octets of metadata value, padded to a multiple of 8 bytes. */
     uint8_t data[0];
 };
+
+struct ofpact_ed_prop_mpls_ethertype {
+    struct ofpact_ed_prop header;
+    uint16_t ether_type;     /* MPLS ethertype . */
+    uint8_t pad[2];          /* Padding to 8 bytes. */
+};
+
 enum ofperr decode_ed_prop(const struct ofp_ed_prop_header **ofp_prop,
                            struct ofpbuf *out, size_t *remaining);
 enum ofperr encode_ed_prop(const struct ofpact_ed_prop **prop,
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index 75f381ec1..140302a90 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -8304,6 +8304,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 8c4aed47b..99aba3cae 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..709f4f31a 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,10 @@  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:
+            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 +8669,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 +8718,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 +8726,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..8b74386b1 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -4468,6 +4468,7 @@  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:
         /* Add supported encap header types here. */
         break;
     default:
@@ -4519,6 +4520,8 @@  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 {
         return false;
     }
@@ -4600,6 +4603,8 @@  format_encap_pkt_type(const ovs_be32 pkt_type)
         return "ethernet";
     case PT_NSH:
         return "nsh";
+    case PT_MPLS:
+        return "mpls";
     default:
         return "UNKNOWN";
     }
diff --git a/lib/ofp-ed-props.c b/lib/ofp-ed-props.c
index 02a9235d5..14dfbae1e 100644
--- a/lib/ofp-ed-props.c
+++ b/lib/ofp-ed-props.c
@@ -79,6 +79,27 @@  decode_ed_prop(const struct ofp_ed_prop_header **ofp_prop,
         }
         break;
     }
+    case OFPPPC_MPLS: {
+        switch (prop_type) {
+        case OFPPPT_PROP_MPLS_ETHERTYPE: {
+            struct ofp_ed_prop_mpls_ethertype *opmet =
+                ALIGNED_CAST(struct ofp_ed_prop_mpls_ethertype *, *ofp_prop);
+            if (len > sizeof(*opmet) || len > *remaining) {
+                return OFPERR_NXBAC_BAD_ED_PROP;
+            }
+            struct ofpact_ed_prop_mpls_ethertype *pmet =
+                    ofpbuf_put_uninit(out, sizeof(*pmet));
+            pmet->header.prop_class = prop_class;
+            pmet->header.type = prop_type;
+            pmet->header.len = len;
+            pmet->ether_type = opmet->ether_type;
+            break;
+        }
+        default:
+            return OFPERR_NXBAC_UNKNOWN_ED_PROP;
+        }
+        break;
+    }
     default:
         return OFPERR_NXBAC_UNKNOWN_ED_PROP;
     }
@@ -134,6 +155,26 @@  encode_ed_prop(const struct ofpact_ed_prop **prop,
         }
         break;
     }
+    case OFPPPC_MPLS: {
+        switch ((*prop)->type) {
+        case OFPPPT_PROP_MPLS_ETHERTYPE: {
+            struct ofpact_ed_prop_mpls_ethertype *pmet =
+                ALIGNED_CAST(struct ofpact_ed_prop_mpls_ethertype *, *prop);
+            struct ofp_ed_prop_mpls_ethertype *opmet =
+                    ofpbuf_put_uninit(out, sizeof(*opmet));
+            opmet->header.prop_class = htons((*prop)->prop_class);
+            opmet->header.type = (*prop)->type;
+            opmet->header.len =
+                    offsetof(struct ofpact_ed_prop_mpls_ethertype, pad);
+            opmet->ether_type = pmet->ether_type;
+            prop_len = sizeof(*pmet);
+            break;
+        }
+        default:
+            return OFPERR_OFPBAC_BAD_ARGUMENT;
+        }
+        break;
+    }
     default:
         return OFPERR_OFPBAC_BAD_ARGUMENT;
     }
@@ -181,6 +222,13 @@  parse_ed_prop_type(uint16_t prop_class,
         } else {
             return false;
         }
+    case OFPPPC_MPLS:
+        if (!strcmp(str, "ether_type")) {
+            *type = OFPPPT_PROP_MPLS_ETHERTYPE;
+            return true;
+        } else {
+            return false;
+        }
     default:
         return false;
     }
@@ -259,6 +307,29 @@  parse_ed_prop_value(uint16_t prop_class, uint8_t prop_type OVS_UNUSED,
             OVS_NOT_REACHED();
         }
         break;
+    case OFPPPC_MPLS:
+        switch (prop_type) {
+        case OFPPPT_PROP_MPLS_ETHERTYPE: {
+            uint16_t ethertype;
+            error = str_to_u16(value, "ether_type", &ethertype);
+            if (error != NULL) {
+                return error;
+            }
+            struct ofpact_ed_prop_mpls_ethertype *pmet =
+                    ofpbuf_put_uninit(out, sizeof(*pmet));
+            pmet->header.prop_class = prop_class;
+            pmet->header.type = prop_type;
+            pmet->header.len =
+                    offsetof(struct ofpact_ed_prop_mpls_ethertype, pad);
+            pmet->ether_type = ethertype;
+
+            break;
+        }
+        default:
+            /* Unsupported property types rejected before. */
+            OVS_NOT_REACHED();
+        }
+        break;
     default:
         /* Unsupported property classes rejected before. */
         OVS_NOT_REACHED();
@@ -300,6 +371,14 @@  format_ed_prop_type(const struct ofpact_ed_prop *prop)
             OVS_NOT_REACHED();
         }
         break;
+    case OFPPPC_MPLS:
+        switch (prop->type) {
+        case OFPPPT_PROP_MPLS_ETHERTYPE:
+            return "ether_type";
+        default:
+            OVS_NOT_REACHED();
+        }
+        break;
     default:
         OVS_NOT_REACHED();
     }
@@ -332,6 +411,18 @@  format_ed_prop(struct ds *s OVS_UNUSED,
         default:
             OVS_NOT_REACHED();
         }
+    case OFPPPC_MPLS:
+        switch (prop->type) {
+        case OFPPPT_PROP_MPLS_ETHERTYPE: {
+            struct ofpact_ed_prop_mpls_ethertype *pmet =
+                ALIGNED_CAST(struct ofpact_ed_prop_mpls_ethertype *, prop);
+            ds_put_format(s, "%s=%d", format_ed_prop_type(prop),
+                          pmet->ether_type);
+            return;
+        }
+        default:
+            OVS_NOT_REACHED();
+        }
     default:
         OVS_NOT_REACHED();
     }
diff --git a/lib/packets.c b/lib/packets.c
index 4a7643c5d..d373e1580 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -418,6 +418,39 @@  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 = htonl(PT_MPLS);
+    } 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 +473,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 515bb59b1..f522492b1 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 8723cb4e8..0f68e281f 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -6404,6 +6404,54 @@  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)
+{
+    int n;
+    uint32_t i;
+    uint16_t ether_type;
+    const char *ptr = (char *) encap->props;
+
+    for (i = 0; i < encap->n_props; i++) {
+        struct ofpact_ed_prop *prop_ptr =
+            ALIGNED_CAST(struct ofpact_ed_prop *, ptr);
+        if (prop_ptr->prop_class == OFPPPC_MPLS) {
+            switch (prop_ptr->type) {
+                case OFPPPT_PROP_MPLS_ETHERTYPE: {
+                     struct ofpact_ed_prop_mpls_ethertype *prop_ether_type =
+                        ALIGNED_CAST(struct ofpact_ed_prop_mpls_ethertype *,
+                                     prop_ptr);
+                    ether_type = prop_ether_type->ether_type;
+                    break;
+                }
+            }
+        }
+    }
+
+    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 = htonl(PT_MPLS);
+    n = flow_count_mpls_labels(flow, ctx->wc);
+    flow_push_mpls(flow, n, htons(ether_type), ctx->wc, true);
+    flow->dl_src = eth_addr_zero;
+    flow->dl_dst = eth_addr_zero;
+}
+
 /* 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
@@ -6535,6 +6583,12 @@  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:
+            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();
@@ -6606,6 +6660,30 @@  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);
+             }
+             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;
+             }
+        }
         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..eada8501c 100644
--- a/tests/mpls-xlate.at
+++ b/tests/mpls-xlate.at
@@ -207,3 +207,60 @@  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(ether_type=0x8847)),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
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/system-traffic.at b/tests/system-traffic.at
index c4442c183..ee7120ed5 100644
--- a/tests/system-traffic.at
+++ b/tests/system-traffic.at
@@ -1087,6 +1087,314 @@  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])
+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(ether_type=0x8847)),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])
+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(ether_type=0x8847)),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 - decap header dp-support])
+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])
+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(ether_type=0x8847)),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(ether_type=0x8847)),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(ether_type=0x8847)),set_mpls_label:3, encap(mpls(ether_type=0x8847)),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(ether_type=0x8847)),set_mpls_label:3, encap(mpls(ether_type=0x8847)),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(ether_type=0x8847)),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(ether_type=0x8847)),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()