[ovs-dev] FW: MPLS and VLAN QinQ patch

Ben Pfaff blp at nicira.com
Tue Jun 12 18:11:50 UTC 2012


Ravi, I'm more or less happy with the userspace code here.  I mostly
have style kinds of concerns.  (I haven't fully scrutinized every line
though.)

I did notice that compose_dec_mpls_ttl() decrements "ttl" twice (it
has --ttl in two places).

I believe that Pravin is going to review the kernel portions of this
code now.  Once he's happy, I'll suggest touch-ups for userspace, or
just do them myself.

On Mon, Jun 11, 2012 at 03:00:45PM -0700, ravi kerur wrote:
> Attached latest mpls and qinq patch. It is based off latest git master.
> 
> Incorporates code review comments, takes care of performance issue on offload.
> 
> Thanks,
> Ravi
> 
> On Fri, Jun 1, 2012 at 2:32 PM, ravi kerur <rkerur at gmail.com> wrote:
> > This is based off
> >
> > commit 73c0ce349ba8d13a63a249a56aad0bec6e6caf26
> > Author: Joe Stringer <joe at wand.net.nz>
> > Date:   Tue May 29 00:38:21 2012 +1200
> >
> > it can wait until your return. I will probably do some performance
> > testing in the mean time for both mpls + qinq tcp offload.
> >
> > Thanks,
> > Ravi
> >
> > On Fri, Jun 1, 2012 at 2:29 PM, Ben Pfaff <blp at nicira.com> wrote:
> >> On Thu, May 31, 2012 at 10:16:51AM -0700, ravi kerur wrote:
> >>> I have generated single patch for OF 1.1 and 1.3 changes assuming it
> >>> would be easier for code-review but as I suggested earlier, I will
> >>> probably have 2 separate commits. Please let me know if you want me to
> >>> separate them for code review as well.
> >>
> >> What commit is the patch against?
> >>
> >> I'm on vacation all next week, so this will probably take a while to
> >> review.
> >>
> >> Thanks,
> >>
> >> Ben.

> From ef864e9b793315c7dedcee9d3e7fd091e4b5ea8d Mon Sep 17 00:00:00 2001
> From: Ravi K <rkerur at gmail.com>
> Date: Mon, 11 Jun 2012 14:40:47 -0700
> Subject: [PATCH 1/2] OF 1.1 and 1.3 MPLS changes.
> 
> Incorporated code review comments from Ben.
> Signed-off-by: Ravi K <rkerur at gmail.com>
> ---
>  datapath/actions.c              |  423 +++++++++++++++++++++++++++++++++++++++
>  datapath/datapath.c             |   35 ++++
>  datapath/flow.c                 |   54 +++++-
>  datapath/flow.h                 |   25 +++
>  datapath/tunnel.c               |  220 +++++++++++++++++++-
>  datapath/vport-gre.c            |   15 ++-
>  datapath/vport-internal_dev.c   |    9 +
>  datapath/vport-netdev.c         |  156 +++++++++++++--
>  datapath/vport.h                |    5 +
>  include/linux/openvswitch.h     |   17 ++
>  include/openflow/nicira-ext.h   |   90 +++++++++
>  include/openflow/openflow-1.2.h |    2 +
>  lib/classifier.c                |   55 +++++-
>  lib/classifier.h                |    3 +
>  lib/dpif-netdev.c               |   25 +++
>  lib/flow.c                      |  127 +++++++++++--
>  lib/flow.h                      |   24 ++-
>  lib/learn.c                     |    2 +-
>  lib/meta-flow.c                 |  116 +++++++++++
>  lib/meta-flow.h                 |    9 +
>  lib/nx-match.c                  |   23 ++-
>  lib/nx-match.h                  |    2 +-
>  lib/odp-util.c                  |  241 ++++++++++++++++++++++-
>  lib/odp-util.h                  |    6 +
>  lib/ofp-parse.c                 |   44 ++++
>  lib/ofp-print.c                 |   46 +++++
>  lib/ofp-util.c                  |   76 +++++++-
>  lib/ofp-util.def                |    8 +
>  lib/ofpbuf.c                    |    8 +-
>  lib/ofpbuf.h                    |    1 +
>  lib/packets.c                   |  352 ++++++++++++++++++++++++++++++++
>  lib/packets.h                   |   88 ++++++++
>  ofproto/ofproto-dpif.c          |   97 +++++++++
>  tests/automake.mk               |    6 +
>  tests/odp.at                    |   20 ++
>  tests/ofp-print.at              |    4 +-
>  tests/ofproto-dpif.at           |  140 ++++++++++---
>  tests/ofproto.at                |   10 +-
>  tests/test-mpls.c               |  288 ++++++++++++++++++++++++++
>  utilities/ovs-dpctl.c           |   30 +++-
>  utilities/ovs-ofctl.8.in        |   45 ++++
>  41 files changed, 2844 insertions(+), 103 deletions(-)
>  create mode 100644 tests/test-mpls.c
> 
> diff --git a/datapath/actions.c b/datapath/actions.c
> index 208f260..8fdcb3b 100644
> --- a/datapath/actions.c
> +++ b/datapath/actions.c
> @@ -123,6 +123,403 @@ static int push_vlan(struct sk_buff *skb, const struct ovs_action_push_vlan *vla
>  	return 0;
>  }
>  
> +/* Determine where MPLS header starts
> + * assumes mac_header is already set. */
> +static char *get_mpls_hdr(const struct sk_buff *skb)
> +{
> +	struct ethhdr *eth;
> +	int nh_ofs;
> +	__be16 dl_type = 0;
> +
> +	eth = eth_hdr(skb);
> +	nh_ofs = sizeof(struct ethhdr);
> +	if (likely(ntohs(eth->h_proto) >= ETH_TYPE_MIN))
> +		dl_type = eth->h_proto;
> +
> +	/* Check for a VLAN tag. */
> +	while (dl_type == htons(ETH_P_8021Q) &&
> +			skb->len >= nh_ofs + sizeof(struct vlan_hdr)) {
> +		struct vlan_hdr *vh = (struct vlan_hdr*)(skb->data + nh_ofs);
> +		dl_type = vh->h_vlan_encapsulated_proto;
> +		nh_ofs += sizeof(struct vlan_hdr);
> +	}
> +
> +	return skb_mac_header(skb) + nh_ofs;
> +}
> +
> +/* Determine where second MPLS header starts
> + * assumes mac_header is already set. */
> +static char *get_next_mpls_hdr(const struct sk_buff *skb)
> +{
> +	struct ethhdr *eth;
> +	int nh_ofs;
> +	__be16 dl_type = 0;
> +
> +	eth = eth_hdr(skb);
> +	nh_ofs = sizeof(struct ethhdr);
> +	if (likely(ntohs(eth->h_proto) >= ETH_TYPE_MIN))
> +		dl_type = eth->h_proto;
> +
> +	/* Check for a VLAN tag. */
> +	while (dl_type == htons(ETH_P_8021Q) &&
> +			skb->len >= nh_ofs + sizeof(struct vlan_hdr)) {
> +		struct vlan_hdr *vh = (struct vlan_hdr*)(skb->data + nh_ofs);
> +		dl_type = vh->h_vlan_encapsulated_proto;
> +		nh_ofs += sizeof(struct vlan_hdr);
> +	}
> +
> +	if ((dl_type == htons(ETH_P_MPLS_UC) ||
> +		 dl_type == htons(ETH_P_MPLS_MC)) &&
> +		 skb->len >= nh_ofs + sizeof(struct mpls_hdr)) {
> +		nh_ofs += sizeof(struct mpls_hdr);
> +	}
> +
> +	return skb_mac_header(skb) + nh_ofs;
> +}
> +
> +/* Get ethertype from the header. */
> +static __be16 get_ethertype(struct sk_buff *skb)
> +{
> +	struct ethhdr *eth = eth_hdr(skb);
> +	__be16 eth_type = htons(0);
> +	if (likely(ntohs(eth->h_proto) >= ETH_TYPE_MIN)) {
> +		if (eth->h_proto == htons(ETH_P_8021Q)) {
> +			eth_type = *(__be16 *)(get_mpls_hdr(skb) - 2);
> +			return eth_type;
> +		} else {
> +			return eth->h_proto;
> +		}
> +	} else {
> +		return eth_type;
> +	}
> +}
> +
> +/* Set ethertype in the header. */
> +static void set_ethertype(struct sk_buff *skb, __be16 eth_type)
> +{
> +	struct ethhdr *eth = eth_hdr(skb);
> +	if (likely(ntohs(eth->h_proto) >= ETH_TYPE_MIN)) {
> +		if (eth->h_proto != htons(ETH_P_8021Q)) {
> +			skb->protocol = eth->h_proto = eth_type;
> +        } else {
> +			/* 2 bytes before L2.5(MPLS) or L3 header is the
> +			 * original ethertype.
> +			 */
> +			memcpy((void *)(get_mpls_hdr(skb) - 2), (void *)&eth_type, 2);
> +		}
> +	}
> +	return;
> +}
> +
> +/* Set outermost MPLS ttl of the MPLS Label stack. */
> +static void
> +set_mpls_lse_ttl(__be32 *mpls_lse, __be32 ttl)
> +{
> +    *mpls_lse &= ~htonl(MPLS_TTL_MASK);
> +    *mpls_lse |= ttl & htonl(MPLS_TTL_MASK);
> +}
> +
> +/* Get mpls label from mpls label stack entry. */
> +static u32 mpls_lse_to_label(__be32 mpls_lse)
> +{
> +	return (ntohl(mpls_lse) & MPLS_LABEL_MASK) >> MPLS_LABEL_SHIFT;
> +}
> +
> +/* Get mpls tc from mpls label stack entry. */
> +static u8 mpls_lse_to_tc(__be32 mpls_lse)
> +{
> +	return (ntohl(mpls_lse) & MPLS_TC_MASK) >> MPLS_TC_SHIFT;
> +}
> +
> +/* Get mpls ttl from mpls label stack entry. */
> +static u8 mpls_lse_to_ttl(__be32 mpls_lse)
> +{
> +	return (ntohl(mpls_lse) & MPLS_TTL_MASK) >> MPLS_TTL_SHIFT;
> +}
> +
> +/* Get mpls stack from mpls label stack entry. */
> +static u8 mpls_lse_to_stack(__be32 mpls_lse)
> +{
> +    return (mpls_lse & htonl(MPLS_STACK_MASK)) != 0;
> +}
> +
> +/* Set mpls lse values. */
> +static void set_mpls_lse_values(__be32 *value, u8 ttl, u8 tos,
> +								u32 label, u8 stack)
> +{
> +	*value =  htonl((stack << MPLS_STACK_SHIFT) |
> +					(ttl << MPLS_TTL_SHIFT) |
> +					(tos << MPLS_TC_SHIFT) |
> +					(label << MPLS_LABEL_SHIFT));
> +}
> +
> +/* Get ttl, tos and label associated with L3/L2.5. */
> +static bool get_label_ttl_and_tos(struct sk_buff *skb, __be16 eth_type,
> +								  u8 *ttl, u8 *tos, u32 *label)
> +{
> +	struct iphdr *ip;
> +	struct ipv6hdr *ip6;
> +	__be32 mpls_lse = htonl(0);
> +
> +	switch (ntohs(eth_type)) {
> +	case ETH_P_IP:
> +		ip = ip_hdr(skb);
> +		if (ip == NULL)
> +			return false;
> +		*ttl = ip->ttl;
> +		*tos = (ipv4_get_dsfield(ip) >> 2) & 0x07;
> +		*label = 0; /* IPV4 Explicit NULL label */
> +	    break;
> +
> +	case ETH_P_IPV6:
> +		ip6 = ipv6_hdr(skb);
> +		if (ip6 == NULL)
> +			return false;
> +		*ttl = ip6->hop_limit;
> +		*tos = ipv6_get_dsfield(ip6) & 0x07;
> +		*label = 2; /* IPV6 Explicit NULL label */
> +	    break;
> +
> +	case ETH_P_MPLS_UC:
> +	case ETH_P_MPLS_MC:
> +		mpls_lse = *((__be32 *)get_mpls_hdr(skb));
> +		*ttl = mpls_lse_to_ttl(mpls_lse);
> +		*tos = mpls_lse_to_tc(mpls_lse);
> +		*label = mpls_lse_to_label(mpls_lse);
> +	    break;
> +
> +	default:
> +		*ttl = 64;
> +		*tos = 0;
> +		*label = 0;
> +	    break;
> +	}
> +	return true;
> +}
> +
> +/* Remove the top label in the MPLS label stack. */
> +static void pop_mpls_lse(struct sk_buff *skb, struct mpls_hdr *mpls_h)
> +{
> +	u32 offset = (uintptr_t)get_mpls_hdr(skb) - (uintptr_t)skb->data;
> +
> +	/* Move everything up to L2.5 up 4 bytes. */
> +	memmove((void *)skb->data + sizeof(struct mpls_hdr), skb->data, offset);
> +
> +	/* Pull offset + size. */
> +	skb_pull(skb, sizeof(struct mpls_hdr));
> +
> +	/* Reset poniter to L2. */
> +	skb_reset_mac_header(skb);
> +}
> +
> +/* Add an MPLS label to the top off the label stack. */
> +static int push_mpls_lse(struct sk_buff *skb, struct mpls_hdr *mpls_h)
> +{
> +	/* Bytes before L2.5. */
> +	u32 offset = (uintptr_t)get_mpls_hdr(skb) - (uintptr_t)skb->data;
> +
> +	/* Make sure there's room. */
> +	if (skb_cow_head(skb, MPLS_HLEN) < 0) {
> +		kfree_skb(skb);
> +		return 1;
> +	}
> +
> +	/* Make room for new label by adding 4 bytes. */
> +	skb_push(skb, MPLS_HLEN);
> +
> +	/* Reset pointer to L2. */
> +	skb_reset_mac_header(skb);
> +
> +	/* Move L2 header + vlan(if any) to make room for new label. */
> +	memmove((void *)skb->data, (void *)skb->data + MPLS_HLEN, offset);
> +
> +	*((__be32*)get_mpls_hdr(skb)) = mpls_h->mpls_lse;
> +	return 0;
> +}
> +
> +/* Pop MPLS header from a packet. */
> +static int pop_mpls(struct sk_buff *skb,
> +                    __be16 pop_ethertype)
> +{
> +	struct ethhdr *eth;
> +	struct mpls_hdr mpls_h;
> +	__be16 eth_proto;
> +
> +	eth_proto = get_ethertype(skb);
> +
> +	eth = eth_hdr(skb);
> +	if (eth_proto == htons(ETH_P_MPLS_UC) ||
> +		eth_proto == htons(ETH_P_MPLS_MC)) {
> +
> +		/* Grab the MLPS label at the top of the stack. */
> +		mpls_h.mpls_lse = *((__be32*)get_mpls_hdr(skb));
> +		pop_mpls_lse(skb, &mpls_h);
> +
> +		if (mpls_h.mpls_lse & htonl(MPLS_STACK_MASK)) {
> +			set_ethertype(skb, pop_ethertype);
> +		}
> +	}
> +	return 0;
> +}
> +
> +/* Push a new MPLS header onto packet. */
> +static int push_mpls(struct sk_buff *skb,
> +                     __be16 push_ethertype)
> +{
> +	struct mpls_hdr mpls_h;
> +	__be16 eth_proto;
> +	u32 label;
> +	u8 ttl, tos;
> +
> +	eth_proto = get_ethertype(skb);
> +
> +	if (get_label_ttl_and_tos(skb, eth_proto, &ttl, &tos, &label)) {
> +		/* First check whether there is another label on the stack. */
> +		if (eth_proto == htons(ETH_P_MPLS_UC) ||
> +			eth_proto == htons(ETH_P_MPLS_MC)) {
> +			set_mpls_lse_values(&mpls_h.mpls_lse, ttl, tos, label, 0);
> +		} else {
> +			/* IPV4/IPv6, VLAN, QinQ or could be vpls. */
> +			set_mpls_lse_values(&mpls_h.mpls_lse, ttl, tos, label, 1);
> +		}
> +
> +		/* push the new label. */
> +		if (!push_mpls_lse(skb, &mpls_h)) {
> +			/* Also change the Ethertype to MPLS. */
> +			set_ethertype(skb, push_ethertype);
> +			return 0;
> +		}
> +	}
> +	return 1;
> +}
> +
> +/* Change MPLS label stack entry. */
> +static int set_mpls_lse(struct sk_buff *skb, __be32 mpls_lse)
> +{
> +	struct mpls_hdr mpls_h;
> +	u32 label;
> +	u8 tc, ttl, stack;
> +	__be16 eth_proto;
> +	__be32 mpls_value;
> +
> +	eth_proto = get_ethertype(skb);
> +
> +	if (eth_proto == htons(ETH_P_MPLS_UC) ||
> +		eth_proto == htons(ETH_P_MPLS_MC)) {
> +
> +		mpls_value = *((__be32 *)get_mpls_hdr(skb));
> +
> +		label = mpls_lse_to_label(mpls_lse);
> +		tc = mpls_lse_to_tc(mpls_lse);
> +		ttl = mpls_lse_to_ttl(mpls_lse);
> +		stack = mpls_lse_to_stack(mpls_value);
> +		set_mpls_lse_values(&mpls_h.mpls_lse, ttl, tc, label, stack);
> +
> +		*((__be32 *)get_mpls_hdr(skb)) = mpls_h.mpls_lse;
> +	}
> +
> +	return 0;
> +}
> +
> +/* Decrement MPLS TTL in the packet. */
> +static int dec_mpls_ttl(struct sk_buff *skb)
> +{
> +	struct mpls_hdr mpls_h;
> +	__be16 eth_proto;
> +	u8 ttl;
> +
> +	eth_proto = get_ethertype(skb);
> +
> +	if (eth_proto == htons(ETH_P_MPLS_UC) ||
> +		eth_proto == htons(ETH_P_MPLS_MC)) {
> +		mpls_h.mpls_lse = *((__be32 *)get_mpls_hdr(skb));
> +		if (mpls_h.mpls_lse & htonl(MPLS_TTL_MASK)) {
> +			ttl = mpls_lse_to_ttl(mpls_h.mpls_lse) - 1;
> +			set_mpls_lse_ttl(&mpls_h.mpls_lse, htonl(ttl));
> +			*((__be32 *)get_mpls_hdr(skb)) = mpls_h.mpls_lse;
> +		}
> +	}
> +	return 0;
> +}
> +
> +/* Helper function to copy MPLS TTL inwards. */
> +static void copy_ttl_in(struct sk_buff *skb, u8 new_ttl)
> +{
> +	struct mpls_hdr mpls_nh;
> +	mpls_nh.mpls_lse = *((__be32 *)get_next_mpls_hdr(skb));
> +	set_mpls_lse_ttl(&mpls_nh.mpls_lse, htonl(new_ttl));
> +	*((__be32 *)get_next_mpls_hdr(skb)) = mpls_nh.mpls_lse;
> +	return;
> +}
> +
> +/* Copy MPLS TTL into IP/MPLS TTL. */
> +static int copy_mpls_ttl_in(struct sk_buff *skb)
> +{
> +	struct mpls_hdr mpls_h;
> +	__be16 eth_proto;
> +
> +	eth_proto = get_ethertype(skb);
> +
> +	if (eth_proto == htons(ETH_P_MPLS_UC) ||
> +		eth_proto == htons(ETH_P_MPLS_MC)) {
> +		u8 new_ttl;
> +		mpls_h.mpls_lse = *((__be32 *)get_mpls_hdr(skb));
> +		new_ttl = mpls_lse_to_ttl(mpls_h.mpls_lse);
> +		if (mpls_h.mpls_lse & htonl(MPLS_STACK_MASK)) {
> +			if (ip_hdr(skb)->version == 4) {
> +				csum_replace2(&ip_hdr(skb)->check, htons(ip_hdr(skb)->ttl << 8),
> +							  htons(new_ttl << 8));
> +				ip_hdr(skb)->ttl = new_ttl;
> +			} else if (ipv6_hdr(skb)->version == 6) {
> +				ipv6_hdr(skb)->hop_limit = new_ttl;
> +			}
> +		} else {
> +			copy_ttl_in(skb, new_ttl);
> +		}
> +	}
> +	return 0;
> +}
> +
> +/* Helper function to copy MPLS TTL outwards. */
> +static void copy_ttl_out(struct sk_buff *skb, struct mpls_hdr mpls_h)
> +{
> +	struct mpls_hdr mpls_nh;
> +	u8 ttl;
> +	mpls_nh.mpls_lse = *((__be32 *)get_next_mpls_hdr(skb));
> +	ttl = mpls_lse_to_ttl(mpls_nh.mpls_lse);
> +	set_mpls_lse_ttl(&mpls_h.mpls_lse, htonl(ttl));
> +	*((__be32 *)get_mpls_hdr(skb)) = mpls_h.mpls_lse;
> +}
> +
> +/* Copy IP/MPLS TTL into outer MPLS header. */
> +static int copy_mpls_ttl_out(struct sk_buff *skb)
> +{
> +	struct mpls_hdr mpls_h;
> +	__be16 eth_proto;
> +	u8  ttl;
> +
> +	eth_proto = get_ethertype(skb);
> +
> +	if (eth_proto == htons(ETH_P_MPLS_UC) ||
> +		eth_proto == htons(ETH_P_MPLS_MC)) {
> +		mpls_h.mpls_lse = *((__be32 *)get_mpls_hdr(skb));
> +		if (mpls_h.mpls_lse & htonl(MPLS_STACK_MASK)) {
> +			if (ip_hdr(skb)->version == 4) {
> +				ttl = ip_hdr(skb)->ttl;
> +			} else if (ipv6_hdr(skb)->version == 6) {
> +				ttl = ipv6_hdr(skb)->hop_limit;
> +			} else {
> +				ttl = 64;
> +			}
> +			set_mpls_lse_ttl(&mpls_h.mpls_lse, htonl(ttl));
> +			*((__be32 *)get_mpls_hdr(skb)) = mpls_h.mpls_lse;
> +		} else {
> +			copy_ttl_out(skb, mpls_h);
> +		}
> +	}
> +	return 0;
> +}
> +
>  static int set_eth_addr(struct sk_buff *skb,
>  			const struct ovs_key_ethernet *eth_key)
>  {
> @@ -406,6 +803,32 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
>  			err = pop_vlan(skb);
>  			break;
>  
> +		case OVS_ACTION_ATTR_PUSH_MPLS:
> +			err = push_mpls(skb, nla_get_be16(a));
> +			if (unlikely(err)) /* skb already freed. */
> +				return err;
> +			break;
> +
> +		case OVS_ACTION_ATTR_POP_MPLS:
> +			err = pop_mpls(skb, nla_get_be16(a));
> +			break;
> +
> +		case OVS_ACTION_ATTR_SET_MPLS_LSE:
> +			err = set_mpls_lse(skb, nla_get_be32(a));
> +			break;
> +
> +		case OVS_ACTION_ATTR_DEC_MPLS_TTL:
> +			err = dec_mpls_ttl(skb);
> +			break;
> +
> +		case OVS_ACTION_ATTR_COPY_TTL_IN:
> +			err = copy_mpls_ttl_in(skb);
> +			break;
> +
> +		case OVS_ACTION_ATTR_COPY_TTL_OUT:
> +			err = copy_mpls_ttl_out(skb);
> +			break;
> +
>  		case OVS_ACTION_ATTR_SET:
>  			err = execute_set_action(skb, nla_data(a));
>  			break;
> diff --git a/datapath/datapath.c b/datapath/datapath.c
> index 605253d..766b8f4 100644
> --- a/datapath/datapath.c
> +++ b/datapath/datapath.c
> @@ -664,10 +664,19 @@ static int validate_actions(const struct nlattr *attr,
>  			[OVS_ACTION_ATTR_USERSPACE] = (u32)-1,
>  			[OVS_ACTION_ATTR_PUSH_VLAN] = sizeof(struct ovs_action_push_vlan),
>  			[OVS_ACTION_ATTR_POP_VLAN] = 0,
> +			[OVS_ACTION_ATTR_PUSH_MPLS] = sizeof(__be16),
> +			[OVS_ACTION_ATTR_POP_MPLS] = sizeof(__be16),
> +			[OVS_ACTION_ATTR_SET_MPLS_LSE] = sizeof(__be32),
> +			[OVS_ACTION_ATTR_DEC_MPLS_TTL] = 0,
> +			[OVS_ACTION_ATTR_COPY_TTL_IN] = 0,
> +			[OVS_ACTION_ATTR_COPY_TTL_OUT] = 0,
>  			[OVS_ACTION_ATTR_SET] = (u32)-1,
>  			[OVS_ACTION_ATTR_SAMPLE] = (u32)-1
>  		};
>  		const struct ovs_action_push_vlan *vlan;
> +		__be16 push_ethertype;
> +		__be16 pop_ethertype;
> +		__be32 mpls_lse;
>  		int type = nla_type(a);
>  
>  		if (type > OVS_ACTION_ATTR_MAX ||
> @@ -702,6 +711,32 @@ static int validate_actions(const struct nlattr *attr,
>  				return -EINVAL;
>  			break;
>  
> +		case OVS_ACTION_ATTR_PUSH_MPLS:
> +			push_ethertype = nla_get_be16(a);
> +			if (push_ethertype != htons(ETH_P_MPLS_UC) &&
> +				push_ethertype != htons(ETH_P_MPLS_MC))
> +				return -EINVAL;
> +			break;
> +
> +		case OVS_ACTION_ATTR_POP_MPLS:
> +			pop_ethertype = nla_get_be16(a);
> +			if (pop_ethertype == htons(ETH_P_MPLS_UC) ||
> +				pop_ethertype == htons(ETH_P_MPLS_MC))
> +				return -EINVAL;
> +			break;
> +
> +		case OVS_ACTION_ATTR_SET_MPLS_LSE:
> +			mpls_lse = nla_get_be32(a);
> +			if (mpls_lse == htonl(0)) {
> +				return -EINVAL;
> +			}
> +			break;
> +
> +		case OVS_ACTION_ATTR_DEC_MPLS_TTL:
> +		case OVS_ACTION_ATTR_COPY_TTL_IN:
> +		case OVS_ACTION_ATTR_COPY_TTL_OUT:
> +			break;
> +
>  		case OVS_ACTION_ATTR_SET:
>  			err = validate_set(a, key);
>  			if (err)
> diff --git a/datapath/flow.c b/datapath/flow.c
> index d07337c..d4d2a58 100644
> --- a/datapath/flow.c
> +++ b/datapath/flow.c
> @@ -479,6 +479,26 @@ static int parse_vlan(struct sk_buff *skb, struct sw_flow_key *key)
>  	return 0;
>  }
>  
> +static int parse_mpls(struct sk_buff *skb, struct sw_flow_key *key)
> +{
> +	struct mtag_prefix {
> +		__be32 mpls_lse;
> +	};
> +	struct mtag_prefix *mp;
> +
> +	if (unlikely(skb->len < sizeof(struct mtag_prefix)))
> +		return 0;
> +
> +	if (unlikely(!pskb_may_pull(skb, sizeof(struct mtag_prefix))))
> +		return -ENOMEM;
> +
> +	mp = (struct mtag_prefix *) skb->data;
> +	key->mpls.mpls_lse = mp->mpls_lse;
> +	__skb_pull(skb, sizeof(struct mtag_prefix));
> +
> +	return 0;
> +}
> +
>  static __be16 parse_ethertype(struct sk_buff *skb)
>  {
>  	struct llc_snap_hdr {
> @@ -612,7 +632,8 @@ out:
>   *    - skb->mac_header: the Ethernet header.
>   *
>   *    - skb->network_header: just past the Ethernet header, or just past the
> - *      VLAN header, to the first byte of the Ethernet payload.
> + *      VLAN header, or just past MPLS shim header to the first byte of the
> + *      Ethernet payload.
>   *
>   *    - skb->transport_header: If key->dl_type is ETH_P_IP or ETH_P_IPV6
>   *      on output, then just past the IP header, if one is present and
> @@ -653,6 +674,13 @@ int ovs_flow_extract(struct sk_buff *skb, u16 in_port, struct sw_flow_key *key,
>  	if (unlikely(key->eth.type == htons(0)))
>  		return -ENOMEM;
>  
> +	if (key->eth.type == htons(ETH_P_MPLS_UC) ||
> +	    key->eth.type == htons(ETH_P_MPLS_MC)) {
> +		if (unlikely(parse_mpls(skb, key)))
> +			return -ENOMEM;
> +		key_len = SW_FLOW_KEY_OFFSET(mpls.mpls_lse);
> +	}
> +
>  	skb_reset_network_header(skb);
>  	__skb_push(skb, skb->data - skb_mac_header(skb));
>  
> @@ -844,6 +872,7 @@ const int ovs_key_lens[OVS_KEY_ATTR_MAX + 1] = {
>  	[OVS_KEY_ATTR_ICMPV6] = sizeof(struct ovs_key_icmpv6),
>  	[OVS_KEY_ATTR_ARP] = sizeof(struct ovs_key_arp),
>  	[OVS_KEY_ATTR_ND] = sizeof(struct ovs_key_nd),
> +	[OVS_KEY_ATTR_MPLS] = sizeof(__be32),
>  
>  	/* Not upstream. */
>  	[OVS_KEY_ATTR_TUN_ID] = sizeof(__be64),
> @@ -1076,6 +1105,22 @@ int ovs_flow_from_nlattrs(struct sw_flow_key *swkey, int *key_lenp,
>  		swkey->eth.type = htons(ETH_P_802_2);
>  	}
>  
> +	if (swkey->eth.type == htons(ETH_P_MPLS_UC) ||
> +	    swkey->eth.type == htons(ETH_P_MPLS_MC)) {
> +		__be32 mpls_lse;
> +
> +		if (!(attrs & (1 << OVS_KEY_ATTR_MPLS))) {
> +			return -EINVAL;
> +		}
> +		mpls_lse = nla_get_be32(a[OVS_KEY_ATTR_MPLS]);
> +		if (mpls_lse & htonl(MPLS_LABEL_MASK | MPLS_TC_MASK)) {
> +			swkey->mpls.mpls_lse = mpls_lse;
> +		}
> +		attrs &= ~(1 << OVS_KEY_ATTR_MPLS);
> +
> +		key_len = SW_FLOW_KEY_OFFSET(mpls.mpls_lse);
> +	}
> +
>  	if (swkey->eth.type == htons(ETH_P_IP)) {
>  		const struct ovs_key_ipv4 *ipv4_key;
>  
> @@ -1242,6 +1287,13 @@ int ovs_flow_to_nlattrs(const struct sw_flow_key *swkey, struct sk_buff *skb)
>  	if (nla_put_be16(skb, OVS_KEY_ATTR_ETHERTYPE, swkey->eth.type))
>  		goto nla_put_failure;
>  
> +	if (swkey->eth.type == htons(ETH_P_MPLS_UC) ||
> +		swkey->eth.type == htons(ETH_P_MPLS_MC)) {
> +		if (nla_put_be32(skb, OVS_KEY_ATTR_MPLS, swkey->mpls.mpls_lse)) {
> +			goto nla_put_failure;
> +		}
> +	}
> +
>  	if (swkey->eth.type == htons(ETH_P_IP)) {
>  		struct ovs_key_ipv4 *ipv4_key;
>  
> diff --git a/datapath/flow.h b/datapath/flow.h
> index 5be481e..f0d040f 100644
> --- a/datapath/flow.h
> +++ b/datapath/flow.h
> @@ -53,6 +53,9 @@ struct sw_flow_key {
>  		__be16 type;		/* Ethernet frame type. */
>  	} eth;
>  	struct {
> +		__be32 mpls_lse;	/* 0 if no MPLS, otherwise MPLS Label Stack entry */
> +	} mpls;
> +	struct {
>  		u8     proto;		/* IP protocol or lower 8 bits of ARP opcode. */
>  		u8     tos;		/* IP ToS. */
>  		u8     ttl;		/* IP TTL/hop limit. */
> @@ -126,6 +129,28 @@ struct arp_eth_header {
>  	unsigned char       ar_tip[4];		/* target IP address        */
>  } __packed;
>  
> +/* Minimum value for an Ethernet type.  Values below this are IEEE 802.2 frame
> + * lengths. */
> +#define ETH_TYPE_MIN           0x600
> +
> +/* MPLS related definitions */
> +#define MPLS_HLEN           4
> +#define MPLS_TTL_MASK       0x000000ff
> +#define MPLS_TTL_SHIFT      0
> +
> +#define MPLS_STACK_MASK     0x00000100
> +#define MPLS_STACK_SHIFT    8
> +
> +#define MPLS_TC_MASK        0x00000e00
> +#define MPLS_TC_SHIFT       9
> +
> +#define MPLS_LABEL_MASK     0xfffff000
> +#define MPLS_LABEL_SHIFT    12
> +
> +struct mpls_hdr {
> +	__be32 mpls_lse;
> +} __packed;
> +
>  int ovs_flow_init(void);
>  void ovs_flow_exit(void);
>  
> diff --git a/datapath/tunnel.c b/datapath/tunnel.c
> index d651c11..3128f6e 100644
> --- a/datapath/tunnel.c
> +++ b/datapath/tunnel.c
> @@ -367,8 +367,77 @@ struct vport *ovs_tnl_find_port(struct net *net, __be32 saddr, __be32 daddr,
>  	return NULL;
>  }
>  
> +/* Check MPLS encapsulated packets. */
> +static __be16 check_skb_mpls(struct sk_buff *skb, __be16 protocol, u8 *err)
> +{
> +	__be32 mpls_lse = htonl(0);
> +	unsigned int mpls_lse_len = MPLS_HLEN;
> +
> +	/* Handle VLAN/MPLS encapsulated packets. */
> +	if (protocol == htons(ETH_P_MPLS_UC) ||
> +		protocol == htons(ETH_P_MPLS_MC)) {
> +		if (unlikely(!pskb_may_pull(skb, MPLS_HLEN))) {
> +			*err = 0;
> +			return protocol;
> +		}
> +
> +		mpls_lse = *(__be32 *)(skb->data + ETH_HLEN);
> +		while (!(mpls_lse & htonl(MPLS_STACK_MASK))) {
> +			if (unlikely(!pskb_may_pull(skb, mpls_lse_len))) {
> +				*err = 0;
> +				return protocol;
> +			}
> +			mpls_lse = *(__be32 *)(skb->data + ETH_HLEN + mpls_lse_len);
> +			mpls_lse_len += MPLS_HLEN;
> +		}
> +		skb_set_network_header(skb, skb_network_offset(skb) + mpls_lse_len);
> +		if (ip_hdr(skb)->version == 4) {
> +			protocol = htons(ETH_P_IP);
> +		} else if (ipv6_hdr(skb)->version == 6) {
> +			protocol = htons(ETH_P_IPV6);
> +		}
> +	}
> +	*err = 1;
> +	return protocol;
> +}
> +
> +/* Check VLAN/MPLS packets. */
> +static __be16 check_skb_vlan_mpls(struct sk_buff *skb, __be16 protocol, u8 *err)
> +{
> +	__be32 mpls_lse = htonl(0);
> +	unsigned int mpls_lse_len = MPLS_HLEN;
> +
> +	/* Handle VLAN/MPLS encapsulated packets. */
> +	if (protocol == htons(ETH_P_MPLS_UC) ||
> +		protocol == htons(ETH_P_MPLS_MC)) {
> +		if (unlikely(!pskb_may_pull(skb, MPLS_HLEN))) {
> +			*err = 0;
> +			return protocol;
> +		}
> +
> +		mpls_lse = *(__be32 *)(skb->data + VLAN_ETH_HLEN);
> +		while (!(mpls_lse & htonl(MPLS_STACK_MASK))) {
> +			if (unlikely(!pskb_may_pull(skb, mpls_lse_len))) {
> +				*err = 0;
> +				return protocol;
> +			}
> +			mpls_lse = *(__be32 *)(skb->data + VLAN_ETH_HLEN + mpls_lse_len);
> +			mpls_lse_len += MPLS_HLEN;
> +		}
> +		skb_set_network_header(skb, skb_network_offset(skb) + mpls_lse_len);
> +		if (ip_hdr(skb)->version == 4) {
> +			protocol = htons(ETH_P_IP);
> +		} else if (ipv6_hdr(skb)->version == 6) {
> +			protocol = htons(ETH_P_IPV6);
> +		}
> +	}
> +	*err = 1;
> +	return protocol;
> +}
> +
>  static void ecn_decapsulate(struct sk_buff *skb, u8 tos)
>  {
> +	u8 rc;
>  	if (unlikely(INET_ECN_is_ce(tos))) {
>  		__be16 protocol = skb->protocol;
>  
> @@ -380,6 +449,19 @@ static void ecn_decapsulate(struct sk_buff *skb, u8 tos)
>  
>  			protocol = vlan_eth_hdr(skb)->h_vlan_encapsulated_proto;
>  			skb_set_network_header(skb, VLAN_ETH_HLEN);
> +
> +			/* Handle VLAN/MPLS encapsulated packets. */
> +			protocol = check_skb_vlan_mpls(skb, protocol, &rc);
> +			if (!rc)
> +				return;
> +		} else {
> +			if (unlikely(!pskb_may_pull(skb, ETH_HLEN)))
> +				return;
> +
> +			/* Handle MPLS encapsulated packets. */
> +			protocol = check_skb_mpls(skb, protocol, &rc);
> +			if (!rc)
> +				return;
>  		}
>  
>  		if (protocol == htons(ETH_P_IP)) {
> @@ -611,11 +693,73 @@ static void ipv6_build_icmp(struct sk_buff *skb, struct sk_buff *nskb,
>  }
>  #endif /* IPv6 */
>  
> +/* Check MPLS stacked packets. Do not modify skb. */
> +void check_mpls_hlen(struct sk_buff *skb, unsigned int *mpls_hlen)
> +{
> +	unsigned int mpls_lse_len = MPLS_HLEN;
> +	__be32 mpls_lse = htonl(0);
> +
> +	if (unlikely(!pskb_may_pull(skb, ETH_HLEN + mpls_lse_len))) {
> +		return;
> +	}
> +
> +	if (skb->protocol == htons(ETH_P_MPLS_UC) ||
> +		skb->protocol == htons(ETH_P_MPLS_MC)) {
> +		*mpls_hlen += MPLS_HLEN;
> +		mpls_lse = *(__be32 *)(skb->data + ETH_HLEN);
> +		while (!(mpls_lse & htonl(MPLS_STACK_MASK))) {
> +			if (unlikely(!pskb_may_pull(skb, mpls_lse_len))) {
> +				return;
> +			}
> +			mpls_lse = *(__be32 *)(skb->data + ETH_HLEN + mpls_lse_len);
> +			*mpls_hlen += MPLS_HLEN;
> +			mpls_lse_len += MPLS_HLEN;
> +		}
> +	}
> +}
> +
> +/* Check VLAN/MPLS packets. Do not modify skb. */
> +void check_vlan_mpls_hlen(struct sk_buff *skb, unsigned int *mpls_hlen)
> +{
> +	__be16 protocol;
> +	__be32 mpls_lse = htonl(0);
> +	unsigned int mpls_lse_len = MPLS_HLEN;
> +	unsigned int total_hlen, eth_type_len;
> +
> +	if (vlan_tx_tag_present(skb)) {
> +		eth_type_len = 2 * ETH_ALEN;
> +	} else {
> +		eth_type_len = 2 * ETH_ALEN + VLAN_HLEN;
> +	}
> +
> +	total_hlen = eth_type_len + 2;
> +
> +	if (unlikely(!pskb_may_pull(skb, total_hlen))) {
> +		return;
> +	}
> +
> +	protocol = *(__be16 *)(skb->data + eth_type_len);
> +	if (protocol == htons(ETH_P_MPLS_UC) ||
> +		protocol == htons(ETH_P_MPLS_MC)) {
> +		*mpls_hlen += MPLS_HLEN;
> +		mpls_lse = *(__be32 *)(skb->data + total_hlen);
> +		while (!(mpls_lse & htonl(MPLS_STACK_MASK))) {
> +			if (unlikely(!pskb_may_pull(skb, mpls_lse_len))) {
> +				return;
> +			}
> +			mpls_lse = *(__be32 *)(skb->data + total_hlen + mpls_lse_len);
> +			*mpls_hlen += MPLS_HLEN;
> +			mpls_lse_len += MPLS_HLEN;
> +		}
> +	}
> +	return;
> +}
> +
>  bool ovs_tnl_frag_needed(struct vport *vport,
>  			 const struct tnl_mutable_config *mutable,
>  			 struct sk_buff *skb, unsigned int mtu, __be64 flow_key)
>  {
> -	unsigned int eth_hdr_len = ETH_HLEN;
> +	unsigned int eth_hdr_len = ETH_HLEN, mpls_hdr_len = 0;
>  	unsigned int total_length = 0, header_length = 0, payload_length;
>  	struct ethhdr *eh, *old_eh = eth_hdr(skb);
>  	struct sk_buff *nskb;
> @@ -648,10 +792,13 @@ bool ovs_tnl_frag_needed(struct vport *vport,
>  		return false;
>  
>  	/* Allocate */
> -	if (old_eh->h_proto == htons(ETH_P_8021Q))
> +	if (old_eh->h_proto == htons(ETH_P_8021Q)) {
> +
>  		eth_hdr_len = VLAN_ETH_HLEN;
> +		check_vlan_mpls_hlen(skb, &mpls_hdr_len);
> +	}
>  
> -	payload_length = skb->len - eth_hdr_len;
> +	payload_length = skb->len - eth_hdr_len - mpls_hdr_len;
>  	if (skb->protocol == htons(ETH_P_IP)) {
>  		header_length = sizeof(struct iphdr) + sizeof(struct icmphdr);
>  		total_length = min_t(unsigned int, header_length +
> @@ -728,26 +875,40 @@ static bool check_mtu(struct sk_buff *skb,
>  	__be16 frag_off = mutable->flags & TNL_F_DF_DEFAULT ? htons(IP_DF) : 0;
>  	int mtu = 0;
>  	unsigned int packet_length = skb->len - ETH_HLEN;
> +	unsigned int mpls_hlen = 0;
>  
> -	/* Allow for one level of tagging in the packet length. */
> +	/* Allow for one level of tagging and allow mpls headers
> +	 * in the packet length. */
>  	if (!vlan_tx_tag_present(skb) &&
> -	    eth_hdr(skb)->h_proto == htons(ETH_P_8021Q))
> +	    eth_hdr(skb)->h_proto == htons(ETH_P_8021Q)) {
> +
>  		packet_length -= VLAN_HLEN;
> +		check_vlan_mpls_hlen(skb, &mpls_hlen);
> +		packet_length = packet_length - mpls_hlen;
> +	}
> +
> +	check_mpls_hlen(skb, &mpls_hlen);
> +	packet_length -= mpls_hlen;
>  
>  	if (pmtud) {
> -		int vlan_header = 0;
> +		unsigned int vlan_header = 0, mpls_header = 0;
>  
>  		/* The tag needs to go in packet regardless of where it
>  		 * currently is, so subtract it from the MTU.
>  		 */
>  		if (vlan_tx_tag_present(skb) ||
> -		    eth_hdr(skb)->h_proto == htons(ETH_P_8021Q))
> +		    eth_hdr(skb)->h_proto == htons(ETH_P_8021Q)) {
>  			vlan_header = VLAN_HLEN;
> +			check_vlan_mpls_hlen(skb, &mpls_header);
> +		}
> +
> +		check_mpls_hlen(skb, &mpls_header);
>  
>  		mtu = dst_mtu(&rt_dst(rt))
>  			- ETH_HLEN
>  			- mutable->tunnel_hlen
> -			- vlan_header;
> +			- vlan_header
> +			- mpls_header;
>  	}
>  
>  	if (skb->protocol == htons(ETH_P_IP)) {
> @@ -1080,10 +1241,13 @@ static struct sk_buff *handle_offloads(struct sk_buff *skb,
>  {
>  	int min_headroom;
>  	int err;
> +	unsigned int mpls_hlen = 0;
>  
> +	check_vlan_mpls_hlen(skb, &mpls_hlen);
>  	min_headroom = LL_RESERVED_SPACE(rt_dst(rt).dev) + rt_dst(rt).header_len
>  			+ mutable->tunnel_hlen
> -			+ (vlan_tx_tag_present(skb) ? VLAN_HLEN : 0);
> +			+ (vlan_tx_tag_present(skb) ? VLAN_HLEN : 0)
> +			+ mpls_hlen;
>  
>  	if (skb_headroom(skb) < min_headroom || skb_header_cloned(skb)) {
>  		int head_delta = SKB_DATA_ALIGN(min_headroom -
> @@ -1099,8 +1263,29 @@ static struct sk_buff *handle_offloads(struct sk_buff *skb,
>  
>  	if (skb_is_gso(skb)) {
>  		struct sk_buff *nskb;
> +		u8 rc = 1;
> +
> +		if (mpls_tag_present(skb)) {
> +			/* skb_gso_segment depends on skb->protocol, save and
> +			 * restore after the call. */
> +			__be16 tmp_protocol = skb->protocol;
> +
> +			skb_set_network_header(skb, ETH_HLEN);
> +
> +			if (unlikely(!pskb_may_pull(skb, ETH_HLEN)))
> +				goto error_free;
> +
> +			/* Handle MPLS encapsulated packets. */
> +			skb->protocol = check_skb_mpls(skb, skb->protocol, &rc);
> +			if (!rc)
> +				goto error_free;
> +
> +            nskb = skb_gso_segment(skb, 0);
> +            skb->protocol = tmp_protocol;
> +		} else {
> +			nskb = skb_gso_segment(skb, 0);
> +		}
>  
> -		nskb = skb_gso_segment(skb, 0);
>  		if (IS_ERR(nskb)) {
>  			kfree_skb(skb);
>  			err = PTR_ERR(nskb);
> @@ -1182,7 +1367,7 @@ int ovs_tnl_send(struct vport *vport, struct sk_buff *skb)
>  	__be16 frag_off = 0;
>  	u8 ttl;
>  	u8 inner_tos;
> -	u8 tos;
> +	u8 tos, rc;
>  
>  	/* Validate the protocol headers before we try to use them. */
>  	if (skb->protocol == htons(ETH_P_8021Q) &&
> @@ -1192,6 +1377,19 @@ int ovs_tnl_send(struct vport *vport, struct sk_buff *skb)
>  
>  		skb->protocol = vlan_eth_hdr(skb)->h_vlan_encapsulated_proto;
>  		skb_set_network_header(skb, VLAN_ETH_HLEN);
> +
> +		/* Handle VLAN/MPLS encapsulated packets. */
> +		skb->protocol = check_skb_vlan_mpls(skb, skb->protocol, &rc);
> +		if (!rc)
> +			goto error_free;
> +	} else {
> +		if (unlikely(!pskb_may_pull(skb, ETH_HLEN)))
> +			goto error_free;
> +
> +		/* Handle MPLS encapsulated packets. */
> +		skb->protocol = check_skb_mpls(skb, skb->protocol, &rc);
> +		if (!rc)
> +			goto error_free;
>  	}
>  
>  	if (skb->protocol == htons(ETH_P_IP)) {
> diff --git a/datapath/vport-gre.c b/datapath/vport-gre.c
> index ab89c5b..471f66b 100644
> --- a/datapath/vport-gre.c
> +++ b/datapath/vport-gre.c
> @@ -101,6 +101,17 @@ static struct sk_buff *gre_update_header(const struct vport *vport,
>  	__be32 *options = (__be32 *)(skb_network_header(skb) + mutable->tunnel_hlen
>  					       - GRE_HEADER_SECTION);
>  
> +	/* GRE IP hdr doesn't carry options, hence ok to rely on iphdr size. */
> +	struct gre_base_hdr *greh =
> +		(struct gre_base_hdr *)(skb_network_header(skb) + sizeof(struct iphdr));
> +
> +	if (skb->protocol == htons(ETH_P_MPLS_UC) ||
> +		skb->protocol == htons(ETH_P_MPLS_MC)) {
> +		/* For MPLS packets tunneled via GRE to MPLS adjacent router, set
> +		 * protocol type to MPLS. */
> +		greh->protocol = skb->protocol;
> +	}
> +
>  	/* Work backwards over the options so the checksum is last. */
>  	if (mutable->flags & TNL_F_OUT_KEY_ACTION)
>  		*options = be64_get_low32(OVS_CB(skb)->tun_id);
> @@ -147,7 +158,9 @@ static int parse_header(struct iphdr *iph, __be16 *flags, __be64 *key)
>  	if (unlikely(greh->flags & (GRE_VERSION | GRE_ROUTING)))
>  		return -EINVAL;
>  
> -	if (unlikely(greh->protocol != htons(ETH_P_TEB)))
> +	if (unlikely(greh->protocol != htons(ETH_P_TEB) &&
> +				 greh->protocol != htons(ETH_P_MPLS_UC) &&
> +				 greh->protocol != htons(ETH_P_MPLS_MC)))
>  		return -EINVAL;
>  
>  	hdr_len = GRE_HEADER_SECTION;
> diff --git a/datapath/vport-internal_dev.c b/datapath/vport-internal_dev.c
> index 4dc2eb4..595e23d 100644
> --- a/datapath/vport-internal_dev.c
> +++ b/datapath/vport-internal_dev.c
> @@ -100,11 +100,20 @@ static int internal_dev_mac_addr(struct net_device *dev, void *p)
>  /* Called with rcu_read_lock_bh. */
>  static int internal_dev_xmit(struct sk_buff *skb, struct net_device *netdev)
>  {
> +	struct ethhdr *eth;
>  	if (unlikely(compute_ip_summed(skb, true))) {
>  		kfree_skb(skb);
>  		return 0;
>  	}
>  
> +	/* Set skb->protocol since some sources don't */
> +	skb_reset_mac_header(skb);
> +        eth = eth_hdr(skb);
> +        if (ntohs(eth->h_proto) >= 1536)
> +		skb->protocol = eth->h_proto;
> +	else
> +		skb->protocol = htons(ETH_P_802_2);
> +
>  	vlan_copy_skb_tci(skb);
>  	OVS_CB(skb)->flow = NULL;
>  
> diff --git a/datapath/vport-netdev.c b/datapath/vport-netdev.c
> index 0098554..d248cf6 100644
> --- a/datapath/vport-netdev.c
> +++ b/datapath/vport-netdev.c
> @@ -45,6 +45,23 @@ MODULE_PARM_DESC(vlan_tso, "Enable TSO for VLAN packets");
>  #define vlan_tso true
>  #endif
>  
> +/* Currently MPLS support is not available in kernel, so use the #define
> + * to control MPLS offload capability.
> + * mpls_tso = 0, use OVS offload simulation.
> + * mpls_tso = 1, use Linux kernel generic offload code.
> + * MPLS_TSO = undefined, use Linux kernel generic offload code. */
> +#define  MPLS_TSO         true
> +
> +#ifdef MPLS_TSO
> +#include <linux/module.h>
> +
> +static int mpls_tso __read_mostly;
> +module_param(mpls_tso, int, 0644);
> +MODULE_PARM_DESC(mpls_tso, "Enable TSO for MPLS packets");
> +#else
> +#define mpls_tso true
> +#endif
> +
>  static void netdev_port_receive(struct vport *vport, struct sk_buff *skb);
>  
>  #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)
> @@ -273,12 +290,22 @@ static void netdev_port_receive(struct vport *vport, struct sk_buff *skb)
>  	ovs_vport_receive(vport, skb);
>  }
>  
> -static unsigned int packet_length(const struct sk_buff *skb)
> +static unsigned int packet_length(struct sk_buff *skb)
>  {
>  	unsigned int length = skb->len - ETH_HLEN;
> +	unsigned int mpls_hlen = 0;
> +
> +	if (skb->protocol == htons(ETH_P_8021Q)) {
>  
> -	if (skb->protocol == htons(ETH_P_8021Q))
>  		length -= VLAN_HLEN;
> +		/* Handle VLAN/MPLS encapsulated packets. */
> +        check_vlan_mpls_hlen(skb, &mpls_hlen);
> +		length -= mpls_hlen;
> +	} else {
> +		/* Handle MPLS encapsulated packets. */
> +		check_mpls_hlen(skb, &mpls_hlen);
> +		length -= mpls_hlen;
> +    }
>  
>  	return length;
>  }
> @@ -296,11 +323,72 @@ static bool dev_supports_vlan_tx(struct net_device *dev)
>  #endif
>  }
>  
> +/* Check for MPLS header presence. */
> +bool mpls_tag_present(struct sk_buff *skb)
> +{
> +#ifdef MPLS_TSO
> +	__be16 protocol = htons(0);
> +	if (skb->protocol == htons(ETH_P_MPLS_UC) ||
> +		skb->protocol == htons(ETH_P_MPLS_MC)) {
> +		return true;
> +	} else if (vlan_tx_tag_present(skb)) {
> +		if (unlikely(!pskb_may_pull(skb, 2 * ETH_ALEN + MPLS_HLEN)))
> +			return false;
> +		protocol = *(__be16 *)(skb->data + 2 * ETH_ALEN);
> +		if (protocol == htons(ETH_P_MPLS_UC) ||
> +			protocol == htons(ETH_P_MPLS_MC)) {
> +			return true;
> +		}
> +	}
> +#endif
> +	return false;
> +}
> +
> +/* Get protocol after MPLS or VLAN/MPLS header. */
> +void check_skb_vlan_mpls_protocol(struct sk_buff *skb)
> +{
> +	int vlan_depth = ETH_HLEN;
> +	int vlan_hlen = 0;
> +
> +	/* Handle MPLS, VLAN/MPLS and VLAN-QinQ/MPLS encapsulated packets. */
> +	while (skb->protocol == htons(ETH_P_8021Q)) {
> +		struct vlan_hdr *vh;
> +
> +		if (unlikely(!pskb_may_pull(skb, vlan_depth + VLAN_HLEN))) {
> +			return;
> +		}
> +
> +		if (vlan_tx_tag_present(skb)) {
> +			skb->protocol = *(__be16*)(skb->data + 2 * ETH_ALEN + vlan_hlen);
> +		} else {
> +			vh = (struct vlan_hdr *)(skb->data + vlan_depth);
> +			skb->protocol = vh->h_vlan_encapsulated_proto;
> +		}
> +		vlan_depth += VLAN_HLEN;
> +		vlan_hlen += VLAN_HLEN;
> +	}
> +
> +	if (skb->protocol == htons(ETH_P_MPLS_UC) ||
> +		skb->protocol == htons(ETH_P_MPLS_MC)) {
> +
> +		if (unlikely(!pskb_may_pull(skb, vlan_depth + MPLS_HLEN + 4))) {
> +			return;
> +		}
> +		if (ip_hdr(skb)->version == 4) {
> +			skb->protocol = htons(ETH_P_IP);
> +		} else if (ipv6_hdr(skb)->version == 6) {
> +			skb->protocol = htons(ETH_P_IPV6);
> +		}
> +	}
> +	return;
> +}
> +
>  static int netdev_send(struct vport *vport, struct sk_buff *skb)
>  {
>  	struct netdev_vport *netdev_vport = netdev_vport_priv(vport);
>  	int mtu = netdev_vport->dev->mtu;
>  	int len;
> +	bool mpls_tag;
>  
>  	if (unlikely(packet_length(skb) > mtu && !skb_is_gso(skb))) {
>  		net_warn_ratelimited("%s: dropped over-mtu packet: %d > %d\n",
> @@ -315,19 +403,36 @@ static int netdev_send(struct vport *vport, struct sk_buff *skb)
>  	skb->dev = netdev_vport->dev;
>  	forward_ip_summed(skb, true);
>  
> -	if (vlan_tx_tag_present(skb) && !dev_supports_vlan_tx(skb->dev)) {
> +	mpls_tag = mpls_tag_present(skb) && !mpls_tso;
> +
> +	/* Handle MPLS and VLAN packets(for kernel < 2.6.37). */
> +	if (mpls_tag ||
> +		(vlan_tx_tag_present(skb) && !dev_supports_vlan_tx(skb->dev))) {
>  		int features;
>  
>  		features = netif_skb_features(skb);
>  
> -		if (!vlan_tso)
> +		if (vlan_tx_tag_present(skb) && !vlan_tso) {
> +			features &= ~(NETIF_F_TSO | NETIF_F_TSO6 |
> +						  NETIF_F_UFO | NETIF_F_FSO);
> +		} else if (mpls_tag) {
>  			features &= ~(NETIF_F_TSO | NETIF_F_TSO6 |
> -				      NETIF_F_UFO | NETIF_F_FSO);
> +						  NETIF_F_UFO | NETIF_F_FSO | NETIF_F_ALL_CSUM);
> +		}
>  
>  		if (netif_needs_gso(skb, features)) {
>  			struct sk_buff *nskb;
>  
> -			nskb = skb_gso_segment(skb, features);
> +			if (mpls_tag) {
> +				/* skb_gso_segment depends on skb->protocol, save and
> +				 * restore after the call. */
> +				__be16 tmp_protocol = skb->protocol;
> +				check_skb_vlan_mpls_protocol(skb);
> +				nskb = skb_gso_segment(skb, features);
> +				skb->protocol = tmp_protocol;
> +			} else {
> +				nskb = skb_gso_segment(skb, features);
> +			}
>  			if (!nskb) {
>  				if (unlikely(skb_cloned(skb) &&
>  				    pskb_expand_head(skb, 0, 0, GFP_ATOMIC))) {
> @@ -351,24 +456,45 @@ static int netdev_send(struct vport *vport, struct sk_buff *skb)
>  				nskb = skb->next;
>  				skb->next = NULL;
>  
> -				skb = __vlan_put_tag(skb, vlan_tx_tag_get(skb));
> -				if (likely(skb)) {
> -					len += skb->len;
> -					vlan_set_tci(skb, 0);
> -					dev_queue_xmit(skb);
> +				/* VLAN packets (kernel < 2.6.37) or VLAN/MPLS packets. */
> +				if (vlan_tx_tag_present(skb)) {
> +					skb = __vlan_put_tag(skb, vlan_tx_tag_get(skb));
> +					if (likely(skb)) {
> +						len += skb->len;
> +						vlan_set_tci(skb, 0);
> +						dev_queue_xmit(skb);
> +					}
> +				} else {
> +					/* MPLS packets. */
> +					if (likely(skb)) {
> +						len += skb->len;
> +						dev_queue_xmit(skb);
> +					}
>  				}
>  
>  				skb = nskb;
>  			} while (skb);
>  
>  			return len;
> +		} else if (mpls_tag &&
> +				   get_ip_summed(skb) == OVS_CSUM_PARTIAL) {
> +			int err;
> +			/* Linearize skb before calculating checksum. */
> +			if (unlikely(skb_linearize(skb)))
> +				goto error;
> +			err = skb_checksum_help(skb);
> +			if (unlikely(err))
> +				goto error;
>  		}
>  
>  tag:
> -		skb = __vlan_put_tag(skb, vlan_tx_tag_get(skb));
> -		if (unlikely(!skb))
> -			return 0;
> -		vlan_set_tci(skb, 0);
> +		/* VLAN packets (kernel < 2.6.37) or VLAN/MPLS packets. */
> +		if (vlan_tx_tag_present(skb)) {
> +			skb = __vlan_put_tag(skb, vlan_tx_tag_get(skb));
> +			if (unlikely(!skb))
> +				return 0;
> +			vlan_set_tci(skb, 0);
> +		}
>  	}
>  
>  	len = skb->len;
> diff --git a/datapath/vport.h b/datapath/vport.h
> index b0cdeae..818ad93 100644
> --- a/datapath/vport.h
> +++ b/datapath/vport.h
> @@ -249,6 +249,11 @@ static inline struct vport *vport_from_priv(const void *priv)
>  
>  void ovs_vport_receive(struct vport *, struct sk_buff *);
>  void ovs_vport_record_error(struct vport *, enum vport_err_type err_type);
> +void check_vlan_mpls_hlen(struct sk_buff *skb, unsigned int *mpls_hlen);
> +void check_mpls_hlen(struct sk_buff *skb, unsigned int *mpls_hlen);
> +void check_skb_vlan_mpls_protocol(struct sk_buff *skb);
> +bool mpls_tag_present(struct sk_buff *skb);
> +
>  
>  /* List of statically compiled vport implementations.  Don't forget to also
>   * add yours to the list at the top of vport.c. */
> diff --git a/include/linux/openvswitch.h b/include/linux/openvswitch.h
> index f5c9cca..63436c7 100644
> --- a/include/linux/openvswitch.h
> +++ b/include/linux/openvswitch.h
> @@ -278,6 +278,7 @@ enum ovs_key_attr {
>  	OVS_KEY_ATTR_ICMPV6,    /* struct ovs_key_icmpv6 */
>  	OVS_KEY_ATTR_ARP,       /* struct ovs_key_arp */
>  	OVS_KEY_ATTR_ND,        /* struct ovs_key_nd */
> +	OVS_KEY_ATTR_MPLS,      /* be32 MPLS Label Stack Entry */
>  	OVS_KEY_ATTR_TUN_ID = 63, /* be64 tunnel ID */
>  	__OVS_KEY_ATTR_MAX
>  };
> @@ -466,6 +467,16 @@ struct ovs_action_push_vlan {
>   * @OVS_ACTION_ATTR_POP_VLAN: Pop the outermost 802.1Q header off the packet.
>   * @OVS_ACTION_ATTR_SAMPLE: Probabilitically executes actions, as specified in
>   * the nested %OVS_SAMPLE_ATTR_* attributes.
> + * @OVS_ACTION_ATTR_PUSH_MPLS: Set the new ethertype and push a new MPLS label.
> + * @OVS_ACTION_ATTR_POP_MPLS: Set the new ethertype and pop the top of stack
> + * MPLS label.
> + * @OVS_ACTION_ATTR_SET_MPLS_LSE: Set the new MPLS label or ttl or traffic class.
> + * @OVS_ACTION_ATTR_DEC_MPLS_TTL: Decrement Time-To-Live associated with MPLS
> + * packet.
> + * @OVS_ACTION_ATTR_COPY_MPLS_TTL_OUT: Copy Time-To-Live from IP/MPLS header to
> + * MPLS header.
> + * @OVS_ACTION_ATTR_COPY_MPLS_TTL_IN: Copy Time-To-Live from MPLS header to
> + * IP header.
>   *
>   * Only a single header can be set with a single %OVS_ACTION_ATTR_SET.  Not all
>   * fields within a header are modifiable, e.g. the IPv4 protocol and fragment
> @@ -480,6 +491,12 @@ enum ovs_action_attr {
>  	OVS_ACTION_ATTR_PUSH_VLAN,    /* struct ovs_action_push_vlan. */
>  	OVS_ACTION_ATTR_POP_VLAN,     /* No argument. */
>  	OVS_ACTION_ATTR_SAMPLE,       /* Nested OVS_SAMPLE_ATTR_*. */
> +	OVS_ACTION_ATTR_PUSH_MPLS,    /* be16, ethertype */
> +	OVS_ACTION_ATTR_POP_MPLS,     /* be16, ethertype */
> +	OVS_ACTION_ATTR_SET_MPLS_LSE, /* be32, mpls label stack entry */
> +	OVS_ACTION_ATTR_DEC_MPLS_TTL, /* No argument */
> +	OVS_ACTION_ATTR_COPY_TTL_OUT, /* No argument */
> +	OVS_ACTION_ATTR_COPY_TTL_IN,  /* No argument */
>  	__OVS_ACTION_ATTR_MAX
>  };
>  
> diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
> index 55d74ed..2e78c49 100644
> --- a/include/openflow/nicira-ext.h
> +++ b/include/openflow/nicira-ext.h
> @@ -338,6 +338,14 @@ enum nx_action_subtype {
>      NXAST_DEC_TTL,              /* struct nx_action_header */
>      NXAST_FIN_TIMEOUT,          /* struct nx_action_fin_timeout */
>      NXAST_CONTROLLER,           /* struct nx_action_controller */
> +    NXAST_COPY_TTL_OUT,         /* struct nx_action_header */
> +    NXAST_COPY_TTL_IN,          /* struct nx_action_header */
> +    NXAST_SET_MPLS_LABEL,       /* struct nx_action_mpls_label */
> +    NXAST_SET_MPLS_TC,          /* struct nx_action_mpls_tc */
> +    NXAST_SET_MPLS_TTL,         /* struct nx_action_mpls_ttl */
> +    NXAST_DEC_MPLS_TTL,         /* struct nx_action_header */
> +    NXAST_PUSH_MPLS,            /* struct nx_action_push_mpls */
> +    NXAST_POP_MPLS,             /* struct nx_action_pop_mpls */
>  };
>  
>  /* Header for Nicira-defined actions. */
> @@ -1758,6 +1766,33 @@ OFP_ASSERT(sizeof(struct nx_action_output_reg) == 24);
>  #define NXM_NX_COOKIE     NXM_HEADER  (0x0001, 30, 8)
>  #define NXM_NX_COOKIE_W   NXM_HEADER_W(0x0001, 30, 8)
>  
> +/* The mpls_label in MPLS shim header.
> + *
> + * Prereqs: NXM_OF_ETH_TYPE must be either 0x8847 or 0x8848.
> + *
> + * Format: 32-bit integer, lower 20 bits
> + *
> + * Masking: Not maskable. */
> +#define NXM_NX_MPLS_LABEL      NXM_HEADER  (0x0001, 31, 4)
> +
> +/* The mpls_tc in MPLS shim header.
> + *
> + * Prereqs: NXM_OF_ETH_TYPE must be either 0x8847 or 0x8848.
> + *
> + * Format: 8-bit integer, lower 3 bits
> + *
> + * Masking: Not maskable. */
> +#define NXM_NX_MPLS_TC      NXM_HEADER  (0x0001, 32, 1)
> +
> +/* The mpls_stack in MPLS shim header.
> + *
> + * Prereqs: NXM_OF_ETH_TYPE must be either 0x8847 or 0x8848.
> + *
> + * Format: 8-bit integer, lower 1 bit
> + *
> + * Masking: Not maskable. */
> +#define NXM_NX_MPLS_STACK   NXM_HEADER  (0x0001, 33, 1)
> +
>  /* ## --------------------- ## */
>  /* ## Requests and replies. ## */
>  /* ## --------------------- ## */
> @@ -1971,4 +2006,59 @@ struct nx_action_controller {
>  };
>  OFP_ASSERT(sizeof(struct nx_action_controller) == 16);
>  
> +/* Action structure for NXAST_SET_MPLS_LABEL. */
> +struct nx_action_mpls_label {
> +    ovs_be16 type;                  /* OFPAT_SET_MPLS_LABEL. */
> +    ovs_be16 len;                   /* Length is 8. */
> +    ovs_be32 vendor;                /* NX_VENDOR_ID. */
> +    ovs_be16 subtype;               /* NXAST_SET_MPLS_LABEL. */
> +    uint8_t pad[2];
> +    ovs_be32 mpls_label;            /* MPLS label in low 20 bits. */
> +};
> +OFP_ASSERT(sizeof(struct nx_action_mpls_label) == 16);
> +
> +/* Action structure for NXAST_SET_MPLS_TC. */
> +struct nx_action_mpls_tc {
> +    ovs_be16 type;                  /* OFPAT_SET_MPLS_TC. */
> +    ovs_be16 len;                   /* Length is 8. */
> +    ovs_be32 vendor;                /* NX_VENDOR_ID. */
> +    ovs_be16 subtype;               /* NXAST_SET_MPLS_TC. */
> +    uint8_t  mpls_tc;               /* MPLS TC */
> +    uint8_t  pad[5];
> +};
> +OFP_ASSERT(sizeof(struct nx_action_mpls_tc) == 16);
> +
> +/* Action structure for NXAST_SET_MPLS_TTL. */
> +struct nx_action_mpls_ttl {
> +    ovs_be16 type;                  /* OFPAT_SET_MPLS_TTL. */
> +    ovs_be16 len;                   /* Length is 8. */
> +    ovs_be32 vendor;                /* NX_VENDOR_ID. */
> +    ovs_be16 subtype;               /* NXAST_SET_MPLS_TTL. */
> +    uint8_t  mpls_ttl;              /* MPLS TTL */
> +    uint8_t  pad[5];
> +};
> +OFP_ASSERT(sizeof(struct nx_action_mpls_ttl) == 16);
> +
> +/* Action structure for NXAST_PUSH_MPLS. */
> +struct nx_action_push_mpls {
> +    ovs_be16 type;                  /* OFPAT_PUSH_MPLS. */
> +    ovs_be16 len;                   /* Length is 8. */
> +    ovs_be32 vendor;                /* NX_VENDOR_ID. */
> +    ovs_be16 subtype;               /* NXAST_PUSH_MPLS. */
> +    ovs_be16 ethertype;             /* Ethertype */
> +    uint8_t  pad[4];
> +};
> +OFP_ASSERT(sizeof(struct nx_action_push_mpls) == 16);
> +
> +/* Action structure for NXAST_POP_MPLS. */
> +struct nx_action_pop_mpls {
> +    ovs_be16 type;                  /* OFPAT_POP_MPLS. */
> +    ovs_be16 len;                   /* Length is 8. */
> +    ovs_be32 vendor;                /* NX_VENDOR_ID. */
> +    ovs_be16 subtype;               /* NXAST_POP_MPLS. */
> +    ovs_be16 ethertype;             /* Ethertype */
> +    uint8_t  pad[4];
> +};
> +OFP_ASSERT(sizeof(struct nx_action_pop_mpls) == 16);
> +
>  #endif /* openflow/nicira-ext.h */
> diff --git a/include/openflow/openflow-1.2.h b/include/openflow/openflow-1.2.h
> index bb55881..300198d 100644
> --- a/include/openflow/openflow-1.2.h
> +++ b/include/openflow/openflow-1.2.h
> @@ -114,6 +114,7 @@ enum oxm12_ofb_match_fields {
>      OFPXMT12_OFB_IPV6_ND_TLL,    /* Target link-layer for ND. */
>      OFPXMT12_OFB_MPLS_LABEL,     /* MPLS label. */
>      OFPXMT12_OFB_MPLS_TC,        /* MPLS TC. */
> +    OFPXMT12_OFB_MPLS_STACK,     /* MPLS stack. */
>  };
>  
>  /* OXM implementation makes use of NXM as they are the same format
> @@ -175,6 +176,7 @@ enum oxm12_ofb_match_fields {
>  #define OXM_OF_IPV6_ND_TLL    OXM_HEADER   (OFPXMT12_OFB_IPV6_ND_TLL, 6)
>  #define OXM_OF_MPLS_LABEL     OXM_HEADER   (OFPXMT12_OFB_MPLS_LABEL, 4)
>  #define OXM_OF_MPLS_TC        OXM_HEADER   (OFPXMT12_OFB_MPLS_TC, 1)
> +#define OXM_OF_MPLS_STACK     OXM_HEADER   (OFPXMT12_OFB_MPLS_STACK, 1)
>  
>  /* The VLAN id is 12-bits, so we can use the entire 16 bits to indicate
>   * special conditions.
> diff --git a/lib/classifier.c b/lib/classifier.c
> index d19840c..d3cb9c0 100644
> --- a/lib/classifier.c
> +++ b/lib/classifier.c
> @@ -263,6 +263,35 @@ cls_rule_set_dl_vlan_pcp(struct cls_rule *rule, uint8_t dl_vlan_pcp)
>      rule->wc.vlan_tci_mask |= htons(VLAN_CFI | VLAN_PCP_MASK);
>  }
>  
> +/* Modifies 'rule' depending on 'mpls_label':
> + * Makes 'rule' match only packets with an MPLS header whose label equals the
> + * low 20 bits of 'mpls_label'. */
> +void
> +cls_rule_set_mpls_label(struct cls_rule *rule, ovs_be32 mpls_label)
> +{
> +    rule->wc.wildcards &= ~FWW_MPLS_LABEL;
> +    flow_set_mpls_label(&rule->flow, mpls_label);
> +}
> +
> +/* Modifies 'rule' so that it matches only packets with an MPLS header whose
> + * Traffic Class equals the low 3 bits of 'mpls_tc'. */
> +void
> +cls_rule_set_mpls_tc(struct cls_rule *rule, uint8_t mpls_tc)
> +{
> +    rule->wc.wildcards &= ~FWW_MPLS_TC;
> +    flow_set_mpls_tc(&rule->flow, mpls_tc);
> +}
> +
> +/* Modifies 'rule' so that it matches only specific packets with
> + * MPLS stack bit set for single label or MPLS stack bit not set for
> + * multiple labels. */
> +void
> +cls_rule_set_mpls_stack(struct cls_rule *rule, uint8_t mpls_stack)
> +{
> +    rule->wc.wildcards &= ~FWW_MPLS_STACK;
> +    flow_set_mpls_stack(&rule->flow, mpls_stack);
> +}
> +
>  void
>  cls_rule_set_tp_src(struct cls_rule *rule, ovs_be16 tp_src)
>  {
> @@ -525,7 +554,7 @@ cls_rule_format(const struct cls_rule *rule, struct ds *s)
>  
>      int i;
>  
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
>  
>      if (rule->priority != OFP_DEFAULT_PRIORITY) {
>          ds_put_format(s, "priority=%d,", rule->priority);
> @@ -567,6 +596,10 @@ cls_rule_format(const struct cls_rule *rule, struct ds *s)
>              }
>          } else if (f->dl_type == htons(ETH_TYPE_ARP)) {
>              ds_put_cstr(s, "arp,");
> +        } else if (f->dl_type == htons(ETH_TYPE_MPLS)) {
> +            ds_put_cstr(s, "mpls,");
> +        } else if (f->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
> +            ds_put_cstr(s, "mplsm,");
>          } else {
>              skip_type = false;
>          }
> @@ -663,6 +696,18 @@ cls_rule_format(const struct cls_rule *rule, struct ds *s)
>      if (!(w & FWW_NW_TTL)) {
>          ds_put_format(s, "nw_ttl=%"PRIu8",", f->nw_ttl);
>      }
> +    if (!(w & FWW_MPLS_LABEL)) {
> +        ds_put_format(s, "mpls_label=%"PRIu32",",
> +                 mpls_lse_to_label(f->mpls_lse));
> +    }
> +    if (!(w & FWW_MPLS_TC)) {
> +        ds_put_format(s, "mpls_tc=%"PRIu8",",
> +                 mpls_lse_to_tc(f->mpls_lse));
> +    }
> +    if (!(w & FWW_MPLS_STACK)) {
> +        ds_put_format(s, "mpls_stack=%"PRIu8",",
> +                 mpls_lse_to_stack(f->mpls_lse));
> +    }
>      switch (wc->nw_frag_mask) {
>      case FLOW_NW_FRAG_ANY | FLOW_NW_FRAG_LATER:
>          ds_put_format(s, "nw_frag=%s,",
> @@ -1188,7 +1233,7 @@ flow_equal_except(const struct flow *a, const struct flow *b,
>      const flow_wildcards_t wc = wildcards->wildcards;
>      int i;
>  
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
>  
>      for (i = 0; i < FLOW_N_REGS; i++) {
>          if ((a->regs[i] ^ b->regs[i]) & wildcards->reg_masks[i]) {
> @@ -1216,6 +1261,12 @@ flow_equal_except(const struct flow *a, const struct flow *b,
>              && (wc & FWW_ARP_SHA || eth_addr_equals(a->arp_sha, b->arp_sha))
>              && (wc & FWW_ARP_THA || eth_addr_equals(a->arp_tha, b->arp_tha))
>              && (wc & FWW_IPV6_LABEL || a->ipv6_label == b->ipv6_label)
> +            && (wc & FWW_MPLS_LABEL ||
> +                !((a->mpls_lse ^ b->mpls_lse) & htonl(MPLS_LABEL_MASK)))
> +            && (wc & FWW_MPLS_TC ||
> +                !((a->mpls_lse ^ b->mpls_lse) & htonl(MPLS_TC_MASK)))
> +            && (wc & FWW_MPLS_STACK ||
> +                !((a->mpls_lse ^ b->mpls_lse) & htonl(MPLS_STACK_MASK)))
>              && ipv6_equal_except(&a->ipv6_src, &b->ipv6_src,
>                      &wildcards->ipv6_src_mask)
>              && ipv6_equal_except(&a->ipv6_dst, &b->ipv6_dst,
> diff --git a/lib/classifier.h b/lib/classifier.h
> index 9e4b33e..9a38d63 100644
> --- a/lib/classifier.h
> +++ b/lib/classifier.h
> @@ -108,6 +108,9 @@ void cls_rule_set_any_vid(struct cls_rule *);
>  void cls_rule_set_dl_vlan(struct cls_rule *, ovs_be16);
>  void cls_rule_set_any_pcp(struct cls_rule *);
>  void cls_rule_set_dl_vlan_pcp(struct cls_rule *, uint8_t);
> +void cls_rule_set_mpls_label(struct cls_rule *, ovs_be32);
> +void cls_rule_set_mpls_tc(struct cls_rule *, uint8_t);
> +void cls_rule_set_mpls_stack(struct cls_rule *, uint8_t);
>  void cls_rule_set_tp_src(struct cls_rule *, ovs_be16);
>  void cls_rule_set_tp_src_masked(struct cls_rule *,
>                                  ovs_be16 port, ovs_be16 mask);
> diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
> index cade79e..a07e0d4 100644
> --- a/lib/dpif-netdev.c
> +++ b/lib/dpif-netdev.c
> @@ -1194,6 +1194,7 @@ execute_set_action(struct ofpbuf *packet, const struct nlattr *a)
>       case OVS_KEY_ATTR_ETHERTYPE:
>       case OVS_KEY_ATTR_IN_PORT:
>       case OVS_KEY_ATTR_VLAN:
> +     case OVS_KEY_ATTR_MPLS:
>       case OVS_KEY_ATTR_ICMP:
>       case OVS_KEY_ATTR_ICMPV6:
>       case OVS_KEY_ATTR_ARP:
> @@ -1235,6 +1236,30 @@ dp_netdev_execute_actions(struct dp_netdev *dp,
>              eth_pop_vlan(packet);
>              break;
>  
> +        case OVS_ACTION_ATTR_PUSH_MPLS:
> +             push_mpls(packet, nl_attr_get_be16(a));
> +             break;
> +
> +        case OVS_ACTION_ATTR_POP_MPLS:
> +             pop_mpls(packet, nl_attr_get_be16(a));
> +             break;
> +
> +        case OVS_ACTION_ATTR_SET_MPLS_LSE:
> +             set_mpls_lse(packet, nl_attr_get_be32(a));
> +             break;
> +
> +         case OVS_ACTION_ATTR_DEC_MPLS_TTL:
> +             dec_mpls_ttl(packet);
> +             break;
> +
> +        case OVS_ACTION_ATTR_COPY_TTL_IN:
> +             copy_mpls_ttl_in(packet);
> +             break;
> +
> +        case OVS_ACTION_ATTR_COPY_TTL_OUT:
> +             copy_mpls_ttl_out(packet);
> +             break;
> +
>          case OVS_ACTION_ATTR_SET:
>              execute_set_action(packet, nl_attr_get(a));
>              break;
> diff --git a/lib/flow.c b/lib/flow.c
> index 46e0e2d..8129724 100644
> --- a/lib/flow.c
> +++ b/lib/flow.c
> @@ -89,6 +89,33 @@ pull_icmpv6(struct ofpbuf *packet)
>  }
>  
>  static void
> +parse_remaining_mpls(struct ofpbuf *b)
> +{
> +    /* Make sure there is some data following MPLS header
> +       before proceeding with parsing remaining MPLS headers. */
> +    while (b->size >= sizeof(struct mpls_hdr) + sizeof(ovs_be16)) {
> +        struct mpls_hdr *mh = b->data;
> +        if (mh->mpls_lse & htonl(MPLS_STACK_MASK)) {
> +            ofpbuf_pull(b, sizeof *mh);
> +            break;
> +         } else {
> +            ofpbuf_pull(b, sizeof *mh);
> +        }
> +    }
> +}
> +
> +static void
> +parse_mpls(struct ofpbuf *b, struct flow *flow)
> +{
> +    /* Make sure there is some data following MPLS header
> +       before proceeding with parsing MPLS headers. */
> +    if (b->size >= sizeof(struct mpls_hdr) + sizeof(ovs_be16)) {
> +        struct mpls_hdr *mp = ofpbuf_pull(b, sizeof *mp);
> +        flow->mpls_lse = mp->mpls_lse;
> +    }
> +}
> +
> +static void
>  parse_vlan(struct ofpbuf *b, struct flow *flow)
>  {
>      struct qtag_prefix {
> @@ -319,6 +346,8 @@ invalid:
>   *
>   *    - packet->l2 to the start of the Ethernet header.
>   *
> + *    - packet->l2_5 to the start of the MPLS shim header.
> + *
>   *    - packet->l3 to just past the Ethernet header, or just past the
>   *      vlan_header if one is present, to the first byte of the payload of the
>   *      Ethernet frame.
> @@ -343,10 +372,11 @@ flow_extract(struct ofpbuf *packet, uint32_t skb_priority, ovs_be64 tun_id,
>      flow->in_port = ofp_in_port;
>      flow->skb_priority = skb_priority;
>  
> -    packet->l2 = b.data;
> -    packet->l3 = NULL;
> -    packet->l4 = NULL;
> -    packet->l7 = NULL;
> +    packet->l2   = b.data;
> +    packet->l2_5 = NULL;
> +    packet->l3   = NULL;
> +    packet->l4   = NULL;
> +    packet->l7   = NULL;
>  
>      if (b.size < sizeof *eth) {
>          return;
> @@ -362,8 +392,18 @@ flow_extract(struct ofpbuf *packet, uint32_t skb_priority, ovs_be64 tun_id,
>      if (eth->eth_type == htons(ETH_TYPE_VLAN)) {
>          parse_vlan(&b, flow);
>      }
> +
>      flow->dl_type = parse_ethertype(&b);
>  
> +    if (flow->dl_type == htons(ETH_TYPE_MPLS) ||
> +        flow->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
> +        packet->l2_5 = b.data;
> +        parse_mpls(&b, flow);
> +        if (!(flow->mpls_lse & htonl(MPLS_STACK_MASK))) {
> +            parse_remaining_mpls(&b);
> +        }
> +    }
> +
>      /* Network layer. */
>      packet->l3 = b.data;
>      if (flow->dl_type == htons(ETH_TYPE_IP)) {
> @@ -444,7 +484,7 @@ flow_zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards)
>      const flow_wildcards_t wc = wildcards->wildcards;
>      int i;
>  
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
>  
>      for (i = 0; i < FLOW_N_REGS; i++) {
>          flow->regs[i] &= wildcards->reg_masks[i];
> @@ -478,6 +518,16 @@ flow_zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards)
>      if (wc & FWW_NW_TTL) {
>          flow->nw_ttl = 0;
>      }
> +    flow->mpls_lse &= ~htonl(MPLS_TTL_MASK);
> +    if (wc & FWW_MPLS_LABEL) {
> +        flow->mpls_lse &= ~htonl(MPLS_LABEL_MASK);
> +    }
> +    if (wc & FWW_MPLS_TC) {
> +        flow->mpls_lse &= ~htonl(MPLS_TC_MASK);
> +    }
> +    if (wc & FWW_MPLS_STACK) {
> +        flow->mpls_lse &= ~htonl(MPLS_STACK_MASK);
> +    }
>      flow->nw_frag &= wildcards->nw_frag_mask;
>      if (wc & FWW_ARP_SHA) {
>          memset(flow->arp_sha, 0, sizeof flow->arp_sha);
> @@ -498,7 +548,7 @@ flow_zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards)
>  void
>  flow_get_metadata(const struct flow *flow, struct flow_metadata *fmd)
>  {
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
>  
>      fmd->tun_id = flow->tun_id;
>      fmd->tun_id_mask = htonll(UINT64_MAX);
> @@ -541,6 +591,18 @@ flow_format(struct ds *ds, const struct flow *flow)
>                    ETH_ADDR_ARGS(flow->dl_dst),
>                    ntohs(flow->dl_type));
>  
> +    ds_put_format(ds, ",mpls(");
> +    if (flow->mpls_lse) {
> +        ds_put_format(ds, "label:%"PRIu32",tc:%d,ttl:%d,bos:%d",
> +                      mpls_lse_to_label(flow->mpls_lse),
> +                      mpls_lse_to_tc(flow->mpls_lse),
> +                      mpls_lse_to_ttl(flow->mpls_lse),
> +                      mpls_lse_to_stack(flow->mpls_lse));
> +    } else {
> +        ds_put_char(ds, '0');
> +    }
> +    ds_put_char(ds, ')');
> +
>      if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
>          ds_put_format(ds, " label:%#"PRIx32" proto:%"PRIu8" tos:%#"PRIx8
>                            " ttl:%"PRIu8" ipv6(",
> @@ -587,7 +649,7 @@ flow_print(FILE *stream, const struct flow *flow)
>  void
>  flow_wildcards_init_catchall(struct flow_wildcards *wc)
>  {
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
>  
>      wc->wildcards = FWW_ALL;
>      wc->tun_id_mask = htonll(0);
> @@ -611,7 +673,7 @@ flow_wildcards_init_catchall(struct flow_wildcards *wc)
>  void
>  flow_wildcards_init_exact(struct flow_wildcards *wc)
>  {
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
>  
>      wc->wildcards = 0;
>      wc->tun_id_mask = htonll(UINT64_MAX);
> @@ -637,7 +699,7 @@ flow_wildcards_is_exact(const struct flow_wildcards *wc)
>  {
>      int i;
>  
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
>  
>      if (wc->wildcards
>          || wc->tun_id_mask != htonll(UINT64_MAX)
> @@ -671,7 +733,7 @@ flow_wildcards_is_catchall(const struct flow_wildcards *wc)
>  {
>      int i;
>  
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
>  
>      if (wc->wildcards != FWW_ALL
>          || wc->tun_id_mask != htonll(0)
> @@ -708,7 +770,7 @@ flow_wildcards_combine(struct flow_wildcards *dst,
>  {
>      int i;
>  
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
>  
>      dst->wildcards = src1->wildcards | src2->wildcards;
>      dst->tun_id_mask = src1->tun_id_mask & src2->tun_id_mask;
> @@ -749,7 +811,7 @@ flow_wildcards_equal(const struct flow_wildcards *a,
>  {
>      int i;
>  
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
>  
>      if (a->wildcards != b->wildcards
>          || a->tun_id_mask != b->tun_id_mask
> @@ -785,7 +847,7 @@ flow_wildcards_has_extra(const struct flow_wildcards *a,
>      uint8_t eth_masked[ETH_ADDR_LEN];
>      struct in6_addr ipv6_masked;
>  
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
>  
>      for (i = 0; i < FLOW_N_REGS; i++) {
>          if ((a->reg_masks[i] & b->reg_masks[i]) != b->reg_masks[i]) {
> @@ -958,6 +1020,39 @@ flow_set_vlan_pcp(struct flow *flow, uint8_t pcp)
>      flow->vlan_tci |= htons((pcp << VLAN_PCP_SHIFT) | VLAN_CFI);
>  }
>  
> +/* Sets the MPLS Label that 'flow' matches to 'label', which is interpreted
> + * as an OpenFlow 1.1 "mpls_label" value. */
> +void
> +flow_set_mpls_label(struct flow *flow, ovs_be32 label)
> +{
> +    if (label == htonl(0)) {
> +        flow->mpls_lse = htonl(0);
> +    } else {
> +        flow->mpls_lse &= ~htonl(MPLS_LABEL_MASK);
> +        flow->mpls_lse |=
> +            htonl((ntohl(label) << MPLS_LABEL_SHIFT) & MPLS_LABEL_MASK);
> +    }
> +}
> +
> +/* Sets the MPLS TC that 'flow' matches to 'tc', which should be in the
> + * range 0...7. */
> +void
> +flow_set_mpls_tc(struct flow *flow, uint8_t tc)
> +{
> +    tc &= 0x07;
> +    flow->mpls_lse &= ~htonl(MPLS_TC_MASK);
> +    flow->mpls_lse |= htonl(tc << MPLS_TC_SHIFT);
> +}
> +
> +/* Sets the MPLS STACK bit that 'flow' matches to which should be 0 or 1. */
> +void
> +flow_set_mpls_stack(struct flow *flow, uint8_t stack)
> +{
> +    stack &= 0x01;
> +    flow->mpls_lse &= ~htonl(MPLS_STACK_MASK);
> +    flow->mpls_lse |= htonl(stack << MPLS_STACK_SHIFT);
> +}
> +
>  /* Puts into 'b' a packet that flow_extract() would parse as having the given
>   * 'flow'.
>   *
> @@ -978,7 +1073,11 @@ flow_compose(struct ofpbuf *b, const struct flow *flow)
>          eth_push_vlan(b, flow->vlan_tci);
>      }
>  
> -    if (flow->dl_type == htons(ETH_TYPE_IP)) {
> +    if (flow->dl_type == htons(ETH_TYPE_MPLS) ||
> +        flow->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
> +        push_mpls(b, flow->dl_type);
> +        set_mpls_lse(b, flow->mpls_lse);
> +    } else if (flow->dl_type == htons(ETH_TYPE_IP)) {
>          struct ip_header *ip;
>  
>          b->l3 = ip = ofpbuf_put_zeros(b, sizeof *ip);
> diff --git a/lib/flow.h b/lib/flow.h
> index 1964115..5cfd456 100644
> --- a/lib/flow.h
> +++ b/lib/flow.h
> @@ -35,7 +35,7 @@ struct ofpbuf;
>  /* This sequence number should be incremented whenever anything involving flows
>   * or the wildcarding of flows changes.  This will cause build assertion
>   * failures in places which likely need to be updated. */
> -#define FLOW_WC_SEQ 11
> +#define FLOW_WC_SEQ 12
>  
>  #define FLOW_N_REGS 8
>  BUILD_ASSERT_DECL(FLOW_N_REGS <= NXM_NX_MAX_REGS);
> @@ -62,6 +62,7 @@ struct flow {
>      ovs_be32 nw_src;            /* IPv4 source address. */
>      ovs_be32 nw_dst;            /* IPv4 destination address. */
>      ovs_be32 ipv6_label;        /* IPv6 flow label. */
> +    ovs_be32 mpls_lse;          /* MPLS label stack entry. */
>      uint16_t in_port;           /* OpenFlow port number of input port. */
>      ovs_be16 vlan_tci;          /* If 802.1Q, TCI | VLAN_CFI; otherwise 0. */
>      ovs_be16 dl_type;           /* Ethernet frame type. */
> @@ -75,7 +76,7 @@ struct flow {
>      uint8_t arp_tha[6];         /* ARP/ND target hardware address. */
>      uint8_t nw_ttl;             /* IP TTL/Hop Limit. */
>      uint8_t nw_frag;            /* FLOW_FRAG_* flags. */
> -    uint8_t reserved[2];        /* Reserved for 64-bit packing. */
> +    uint8_t reserved[6];        /* Reserved for 64-bit packing. */
>  };
>  
>  /* Represents the metadata fields of struct flow.  The masks are used to
> @@ -93,14 +94,14 @@ struct flow_metadata {
>  
>  /* Assert that there are FLOW_SIG_SIZE bytes of significant data in "struct
>   * flow", followed by FLOW_PAD_SIZE bytes of padding. */
> -#define FLOW_SIG_SIZE (110 + FLOW_N_REGS * 4)
> -#define FLOW_PAD_SIZE 2
> +#define FLOW_SIG_SIZE (114 + FLOW_N_REGS * 4)
> +#define FLOW_PAD_SIZE 6
>  BUILD_ASSERT_DECL(offsetof(struct flow, nw_frag) == FLOW_SIG_SIZE - 1);
>  BUILD_ASSERT_DECL(sizeof(((struct flow *)0)->nw_frag) == 1);
>  BUILD_ASSERT_DECL(sizeof(struct flow) == FLOW_SIG_SIZE + FLOW_PAD_SIZE);
>  
>  /* Remember to update FLOW_WC_SEQ when changing 'struct flow'. */
> -BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 142 && FLOW_WC_SEQ == 11);
> +BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 146 && FLOW_WC_SEQ == 12);
>  
>  void flow_extract(struct ofpbuf *, uint32_t priority, ovs_be64 tun_id,
>                    uint16_t in_port, struct flow *);
> @@ -117,6 +118,10 @@ static inline size_t flow_hash(const struct flow *, uint32_t basis);
>  void flow_set_vlan_vid(struct flow *, ovs_be16 vid);
>  void flow_set_vlan_pcp(struct flow *, uint8_t pcp);
>  
> +void flow_set_mpls_label(struct flow *flow, ovs_be32 label);
> +void flow_set_mpls_tc(struct flow *flow, uint8_t tc);
> +void flow_set_mpls_stack(struct flow *flow, uint8_t stack);
> +
>  void flow_compose(struct ofpbuf *, const struct flow *);
>  
>  static inline int
> @@ -156,10 +161,13 @@ typedef unsigned int OVS_BITWISE flow_wildcards_t;
>  #define FWW_ARP_THA     ((OVS_FORCE flow_wildcards_t) (1 << 6))
>  #define FWW_IPV6_LABEL  ((OVS_FORCE flow_wildcards_t) (1 << 7))
>  #define FWW_NW_TTL      ((OVS_FORCE flow_wildcards_t) (1 << 8))
> -#define FWW_ALL         ((OVS_FORCE flow_wildcards_t) (((1 << 9)) - 1))
> +#define FWW_MPLS_LABEL  ((OVS_FORCE flow_wildcards_t) (1 << 9))
> +#define FWW_MPLS_TC     ((OVS_FORCE flow_wildcards_t) (1 << 10))
> +#define FWW_MPLS_STACK  ((OVS_FORCE flow_wildcards_t) (1 << 11))
> +#define FWW_ALL         ((OVS_FORCE flow_wildcards_t) (((1 << 12)) - 1))
>  
>  /* Remember to update FLOW_WC_SEQ when adding or removing FWW_*. */
> -BUILD_ASSERT_DECL(FWW_ALL == ((1 << 9) - 1) && FLOW_WC_SEQ == 11);
> +BUILD_ASSERT_DECL(FWW_ALL == ((1 << 12) - 1) && FLOW_WC_SEQ == 12);
>  
>  /* Information on wildcards for a flow, as a supplement to "struct flow".
>   *
> @@ -185,7 +193,7 @@ struct flow_wildcards {
>  };
>  
>  /* Remember to update FLOW_WC_SEQ when updating struct flow_wildcards. */
> -BUILD_ASSERT_DECL(sizeof(struct flow_wildcards) == 120 && FLOW_WC_SEQ == 11);
> +BUILD_ASSERT_DECL(sizeof(struct flow_wildcards) == 120 && FLOW_WC_SEQ == 12);
>  
>  void flow_wildcards_init_catchall(struct flow_wildcards *);
>  void flow_wildcards_init_exact(struct flow_wildcards *);
> diff --git a/lib/learn.c b/lib/learn.c
> index cbecb10..5478b74 100644
> --- a/lib/learn.c
> +++ b/lib/learn.c
> @@ -184,7 +184,7 @@ learn_check(const struct nx_action_learn *learn, const struct flow *flow)
>                       * prerequisites.  No prerequisite depends on the value of
>                       * a field that is wider than 64 bits.  So just skip
>                       * setting it entirely. */
> -                    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
> +                    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
>                  }
>              }
>          }
> diff --git a/lib/meta-flow.c b/lib/meta-flow.c
> index b97af30..655c814 100644
> --- a/lib/meta-flow.c
> +++ b/lib/meta-flow.c
> @@ -168,6 +168,38 @@ static const struct mf_field mf_fields[MFF_N_IDS] = {
>          OXM_OF_VLAN_PCP, "OXM_OF_VLAN_PCP",
>      },
>  
> +    /* ## ---- ## */
> +    /* ## L2.5 ## */
> +    /* ## ---- ## */
> +    {
> +        MFF_MPLS_LABEL, "mpls_label", NULL,
> +        4, 20,
> +        MFM_NONE, FWW_MPLS_LABEL,
> +        MFS_DECIMAL,
> +        MFP_MPLS,
> +        true,
> +        NXM_NX_MPLS_LABEL, "NXM_NX_MPLS_LABEL",
> +        0, NULL,
> +    }, {
> +        MFF_MPLS_TC, "mpls_tc", NULL,
> +        1, 3,
> +        MFM_NONE, FWW_MPLS_TC,
> +        MFS_DECIMAL,
> +        MFP_MPLS,
> +        true,
> +        NXM_NX_MPLS_TC, "NXM_NX_MPLS_TC",
> +        0, NULL,
> +    }, {
> +        MFF_MPLS_STACK, "mpls_stack", NULL,
> +        1, 1,
> +        MFM_NONE, FWW_MPLS_STACK,
> +        MFS_DECIMAL,
> +        MFP_MPLS,
> +        true,
> +        NXM_NX_MPLS_STACK, "NXM_NX_MPLS_STACK",
> +        0, NULL,
> +    },
> +
>      /* ## -- ## */
>      /* ## L3 ## */
>      /* ## -- ## */
> @@ -585,6 +617,9 @@ mf_is_all_wild(const struct mf_field *mf, const struct flow_wildcards *wc)
>      case MFF_ARP_THA:
>      case MFF_ND_SLL:
>      case MFF_ND_TLL:
> +    case MFF_MPLS_LABEL:
> +    case MFF_MPLS_TC:
> +    case MFF_MPLS_STACK:
>          assert(mf->fww_bit != 0);
>          return (wc->wildcards & mf->fww_bit) != 0;
>  
> @@ -693,6 +728,9 @@ mf_get_mask(const struct mf_field *mf, const struct flow_wildcards *wc,
>      case MFF_ARP_THA:
>      case MFF_ND_SLL:
>      case MFF_ND_TLL:
> +    case MFF_MPLS_LABEL:
> +    case MFF_MPLS_TC:
> +    case MFF_MPLS_STACK:
>          assert(mf->fww_bit != 0);
>          memset(mask, wc->wildcards & mf->fww_bit ? 0x00 : 0xff, mf->n_bytes);
>          break;
> @@ -859,6 +897,9 @@ mf_are_prereqs_ok(const struct mf_field *mf, const struct flow *flow)
>          return flow->dl_type == htons(ETH_TYPE_IP);
>      case MFP_IPV6:
>          return flow->dl_type == htons(ETH_TYPE_IPV6);
> +    case MFP_MPLS:
> +        return (flow->dl_type == htons(ETH_TYPE_MPLS) ||
> +                flow->dl_type == htons(ETH_TYPE_MPLS_MCAST));
>      case MFP_IP_ANY:
>          return is_ip_any(flow);
>  
> @@ -978,6 +1019,15 @@ mf_is_value_valid(const struct mf_field *mf, const union mf_value *value)
>      case MFF_IPV6_LABEL:
>          return !(value->be32 & ~htonl(IPV6_LABEL_MASK));
>  
> +    case MFF_MPLS_LABEL:
> +        return !(value->be32 & ~htonl(MPLS_LABEL_MASK >> MPLS_LABEL_SHIFT));
> +
> +    case MFF_MPLS_TC:
> +        return !(value->u8 & ~7);
> +
> +    case MFF_MPLS_STACK:
> +        return !(value->u8 & ~1);
> +
>      case MFF_N_IDS:
>      default:
>          NOT_REACHED();
> @@ -1053,6 +1103,18 @@ mf_get_value(const struct mf_field *mf, const struct flow *flow,
>          value->u8 = vlan_tci_to_pcp(flow->vlan_tci);
>          break;
>  
> +    case MFF_MPLS_LABEL:
> +        value->be32 = htonl(mpls_lse_to_label(flow->mpls_lse));
> +        break;
> +
> +    case MFF_MPLS_TC:
> +        value->u8 = mpls_lse_to_tc(flow->mpls_lse);
> +        break;
> +
> +    case MFF_MPLS_STACK:
> +        value->u8 = mpls_lse_to_stack(flow->mpls_lse);
> +        break;
> +
>      case MFF_IPV4_SRC:
>          value->be32 = flow->nw_src;
>          break;
> @@ -1259,6 +1321,18 @@ mf_set_value(const struct mf_field *mf,
>          cls_rule_set_nw_ttl(rule, value->u8);
>          break;
>  
> +    case MFF_MPLS_LABEL:
> +        cls_rule_set_mpls_label(rule, value->be32);
> +        break;
> +
> +    case MFF_MPLS_TC:
> +        cls_rule_set_mpls_tc(rule, value->u8);
> +        break;
> +
> +    case MFF_MPLS_STACK:
> +        cls_rule_set_mpls_stack(rule, value->u8);
> +        break;
> +
>      case MFF_IP_FRAG:
>          cls_rule_set_nw_frag(rule, value->u8);
>          break;
> @@ -1393,6 +1467,18 @@ mf_set_flow_value(const struct mf_field *mf,
>          flow_set_vlan_pcp(flow, value->u8);
>          break;
>  
> +    case MFF_MPLS_LABEL:
> +        flow_set_mpls_label(flow, value->be32);
> +        break;
> +
> +    case MFF_MPLS_TC:
> +        flow_set_mpls_tc(flow, value->u8);
> +        break;
> +
> +    case MFF_MPLS_STACK:
> +        flow_set_mpls_stack(flow, value->u8);
> +        break;
> +
>      case MFF_IPV4_SRC:
>          flow->nw_src = value->be32;
>          break;
> @@ -1633,6 +1719,21 @@ mf_set_wild(const struct mf_field *mf, struct cls_rule *rule)
>          rule->flow.nw_ttl = 0;
>          break;
>  
> +    case MFF_MPLS_LABEL:
> +        rule->wc.wildcards |= FWW_MPLS_LABEL;
> +        rule->flow.mpls_lse &= ~htonl(MPLS_LABEL_MASK);
> +        break;
> +
> +    case MFF_MPLS_TC:
> +        rule->wc.wildcards |= FWW_MPLS_TC;
> +        rule->flow.mpls_lse &= ~htonl(MPLS_TC_MASK);
> +        break;
> +
> +    case MFF_MPLS_STACK:
> +        rule->wc.wildcards |= FWW_MPLS_STACK;
> +        rule->flow.mpls_lse &= ~htonl(MPLS_STACK_MASK);
> +        break;
> +
>      case MFF_IP_FRAG:
>          rule->wc.nw_frag_mask |= FLOW_NW_FRAG_MASK;
>          rule->flow.nw_frag &= ~FLOW_NW_FRAG_MASK;
> @@ -1712,6 +1813,9 @@ mf_set(const struct mf_field *mf,
>      case MFF_VLAN_VID:
>      case MFF_VLAN_PCP:
>      case MFF_IPV6_LABEL:
> +    case MFF_MPLS_LABEL:
> +    case MFF_MPLS_TC:
> +    case MFF_MPLS_STACK:
>      case MFF_IP_PROTO:
>      case MFF_IP_TTL:
>      case MFF_IP_DSCP:
> @@ -1972,6 +2076,18 @@ mf_random_value(const struct mf_field *mf, union mf_value *value)
>          value->u8 &= 0x07;
>          break;
>  
> +    case MFF_MPLS_LABEL:
> +        value->be32 &= htonl(MPLS_LABEL_MASK >> MPLS_LABEL_SHIFT);
> +        break;
> +
> +    case MFF_MPLS_TC:
> +        value->u8 &= 0x07;
> +        break;
> +
> +    case MFF_MPLS_STACK:
> +        value->u8 &= 0x01;
> +        break;
> +
>      case MFF_N_IDS:
>      default:
>          NOT_REACHED();
> diff --git a/lib/meta-flow.h b/lib/meta-flow.h
> index 29e3fa7..257ee05 100644
> --- a/lib/meta-flow.h
> +++ b/lib/meta-flow.h
> @@ -71,6 +71,11 @@ enum mf_field_id {
>      MFF_VLAN_VID,               /* be16 */
>      MFF_VLAN_PCP,               /* u8 */
>  
> +    /* L2.5 */
> +    MFF_MPLS_LABEL,             /* be32 */
> +    MFF_MPLS_TC,                /* u8 */
> +    MFF_MPLS_STACK,             /* u8 */
> +
>      /* L3. */
>      MFF_IPV4_SRC,               /* be32 */
>      MFF_IPV4_DST,               /* be32 */
> @@ -122,6 +127,7 @@ enum mf_prereqs {
>  
>      /* L2 requirements. */
>      MFP_ARP,
> +    MFP_MPLS,
>      MFP_IPV4,
>      MFP_IPV6,
>      MFP_IP_ANY,
> @@ -178,6 +184,9 @@ struct mf_field {
>       *     - "dl_vlan_pcp" is 1 byte but only 3 bits.
>       *     - "is_frag" is 1 byte but only 2 bits.
>       *     - "ipv6_label" is 4 bytes but only 20 bits.
> +     *     - "mpls_label" is 4 bytes but only 20 bits.
> +     *     - "mpls_tc"    is 1 byte but only 3 bits.
> +     *     - "mpls_stack" is 1 byte but only 1 bit.
>       */
>      unsigned int n_bytes;       /* Width of the field in bytes. */
>      unsigned int n_bits;        /* Number of significant bits in field. */
> diff --git a/lib/nx-match.c b/lib/nx-match.c
> index 920184c..40fbb04 100644
> --- a/lib/nx-match.c
> +++ b/lib/nx-match.c
> @@ -487,7 +487,7 @@ nx_put_match(struct ofpbuf *b, bool oxm, const struct cls_rule *cr,
>      int match_len;
>      int i;
>  
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
>  
>      /* Metadata. */
>      if (!(wc & FWW_IN_PORT)) {
> @@ -514,6 +514,27 @@ nx_put_match(struct ofpbuf *b, bool oxm, const struct cls_rule *cr,
>       * XXX missing OXM support */
>      nxm_put_16m(b, NXM_OF_VLAN_TCI, flow->vlan_tci, cr->wc.vlan_tci_mask);
>  
> +
> +    /* MPLS. */
> +    if (!(wc & FWW_DL_TYPE) &&
> +       (flow->dl_type == htons(ETH_TYPE_MPLS) ||
> +        flow->dl_type == htons(ETH_TYPE_MPLS_MCAST))) {
> +        if (!(wc & FWW_MPLS_TC)) {
> +            nxm_put_8(b, oxm ? OXM_OF_MPLS_TC : NXM_NX_MPLS_TC,
> +                      mpls_lse_to_tc(flow->mpls_lse));
> +        }
> +
> +        if (!(wc & FWW_MPLS_STACK)) {
> +            nxm_put_8(b, oxm ? OXM_OF_MPLS_STACK : NXM_NX_MPLS_STACK,
> +                      mpls_lse_to_stack(flow->mpls_lse));
> +        }
> +
> +        if (!(wc & FWW_MPLS_LABEL)) {
> +            nxm_put_32(b, oxm ? OXM_OF_MPLS_LABEL : NXM_NX_MPLS_LABEL,
> +                 htonl(mpls_lse_to_label(flow->mpls_lse)));
> +        }
> +    }
> +
>      /* L3. */
>      if (!(wc & FWW_DL_TYPE) && flow->dl_type == htons(ETH_TYPE_IP)) {
>          /* IP. */
> diff --git a/lib/nx-match.h b/lib/nx-match.h
> index 22db477..b8d194b 100644
> --- a/lib/nx-match.h
> +++ b/lib/nx-match.h
> @@ -90,7 +90,7 @@ void nxm_decode(struct mf_subfield *, ovs_be32 header, ovs_be16 ofs_nbits);
>  void nxm_decode_discrete(struct mf_subfield *, ovs_be32 header,
>                           ovs_be16 ofs, ovs_be16 n_bits);
>  
> -BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
> +BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
>  /* Upper bound on the length of an nx_match.  The longest nx_match (an
>   * IPV6 neighbor discovery message using 5 registers) would be:
>   *
> diff --git a/lib/odp-util.c b/lib/odp-util.c
> index 0574c9f..2dfa32f 100644
> --- a/lib/odp-util.c
> +++ b/lib/odp-util.c
> @@ -16,6 +16,7 @@
>  
>  #include <arpa/inet.h>
>  #include <config.h>
> +#include <assert.h>
>  #include "odp-util.h"
>  #include <errno.h>
>  #include <inttypes.h>
> @@ -73,6 +74,12 @@ odp_action_len(uint16_t type)
>      case OVS_ACTION_ATTR_USERSPACE: return -2;
>      case OVS_ACTION_ATTR_PUSH_VLAN: return sizeof(struct ovs_action_push_vlan);
>      case OVS_ACTION_ATTR_POP_VLAN: return 0;
> +    case OVS_ACTION_ATTR_PUSH_MPLS: return sizeof(ovs_be16);
> +    case OVS_ACTION_ATTR_POP_MPLS:  return sizeof(ovs_be16);
> +    case OVS_ACTION_ATTR_SET_MPLS_LSE: return sizeof(ovs_be32);
> +    case OVS_ACTION_ATTR_DEC_MPLS_TTL: return 0;
> +    case OVS_ACTION_ATTR_COPY_TTL_IN: return 0;
> +    case OVS_ACTION_ATTR_COPY_TTL_OUT: return 0;
>      case OVS_ACTION_ATTR_SET: return -2;
>      case OVS_ACTION_ATTR_SAMPLE: return -2;
>  
> @@ -106,6 +113,7 @@ ovs_key_attr_to_string(enum ovs_key_attr attr)
>      case OVS_KEY_ATTR_ARP: return "arp";
>      case OVS_KEY_ATTR_ND: return "nd";
>      case OVS_KEY_ATTR_TUN_ID: return "tun_id";
> +    case OVS_KEY_ATTR_MPLS: return "mpls";
>  
>      case __OVS_KEY_ATTR_MAX:
>      default:
> @@ -273,11 +281,34 @@ format_vlan_tci(struct ds *ds, ovs_be16 vlan_tci)
>  }
>  
>  static void
> +format_mpls_lse(struct ds *ds, ovs_be32 mpls_lse)
> +{
> +    ds_put_format(ds, "label=%"PRIu32",tc=%d,ttl=%d,bos=%d",
> +                  mpls_lse_to_label(mpls_lse),
> +                  mpls_lse_to_tc(mpls_lse),
> +                  mpls_lse_to_ttl(mpls_lse),
> +                  mpls_lse_to_stack(mpls_lse));
> +}
> +
> +static ovs_be32
> +format_mpls_lse_values(int mpls_label, int mpls_tc,
> +                       int mpls_ttl, int mpls_stack)
> +{
> +    return(htonl((mpls_label << MPLS_LABEL_SHIFT) |
> +                 (mpls_tc << MPLS_TC_SHIFT)       |
> +                 (mpls_ttl << MPLS_TTL_SHIFT)     |
> +                 (mpls_stack << MPLS_STACK_SHIFT)));
> +}
> +
> +static void
>  format_odp_action(struct ds *ds, const struct nlattr *a)
>  {
>      int expected_len;
>      enum ovs_action_attr type = nl_attr_type(a);
>      const struct ovs_action_push_vlan *vlan;
> +    ovs_be16 push_ethertype;
> +    ovs_be16 pop_ethertype;
> +    ovs_be32 mpls_lse;
>  
>      expected_len = odp_action_len(nl_attr_type(a));
>      if (expected_len != -2 && nl_attr_get_size(a) != expected_len) {
> @@ -311,6 +342,31 @@ format_odp_action(struct ds *ds, const struct nlattr *a)
>      case OVS_ACTION_ATTR_POP_VLAN:
>          ds_put_cstr(ds, "pop_vlan");
>          break;
> +   case OVS_ACTION_ATTR_PUSH_MPLS:
> +        push_ethertype = nl_attr_get_be16(a);
> +        ds_put_format(ds, "push_mpls(eth_type=0x%"PRIx16")",
> +                      ntohs(push_ethertype));
> +        break;
> +    case OVS_ACTION_ATTR_POP_MPLS:
> +        pop_ethertype = nl_attr_get_be16(a);
> +        ds_put_format(ds, "pop_mpls(eth_type=0x%"PRIx16")",
> +                      ntohs(pop_ethertype));
> +        break;
> +    case OVS_ACTION_ATTR_SET_MPLS_LSE:
> +        mpls_lse = nl_attr_get_be32(a);
> +        ds_put_cstr(ds, "mpls_lse(");
> +        format_mpls_lse(ds, mpls_lse);
> +        ds_put_char(ds, ')');
> +        break;
> +    case OVS_ACTION_ATTR_DEC_MPLS_TTL:
> +        ds_put_format(ds, "dec_mpls_ttl");
> +        break;
> +    case OVS_ACTION_ATTR_COPY_TTL_IN:
> +        ds_put_format(ds, "copy_ttl_in");
> +        break;
> +    case OVS_ACTION_ATTR_COPY_TTL_OUT:
> +        ds_put_format(ds, "copy_ttl_out");
> +        break;
>      case OVS_ACTION_ATTR_SAMPLE:
>          format_odp_sample_action(ds, a);
>          break;
> @@ -511,6 +567,60 @@ parse_odp_action(const char *s, const struct simap *port_names,
>      }
>  
>      {
> +        ovs_be32 mpls_lse;
> +        int label, tc, ttl, bos;
> +        int n = -1;
> +
> +        if ((sscanf(s, "mpls_lse(label=%i,tc=%i,ttl=%i,bos=%i)%n",
> +            &label, &tc, &ttl, &bos, &n) > 0 && n > 0)) {
> +            mpls_lse = format_mpls_lse_values(label, tc, ttl, bos);
> +            nl_msg_put_be32(actions, OVS_ACTION_ATTR_SET_MPLS_LSE, mpls_lse);
> +            return n;
> +        }
> +    }
> +
> +    {
> +        ovs_be16 push_ethertype;
> +        int etype;
> +        int n = -1;
> +
> +        if ((sscanf(s, "push_mpls(eth_type=%i)%n", &etype, &n) > 0 && n > 0)) {
> +            push_ethertype = htons(etype);
> +            nl_msg_put_be16(actions, OVS_ACTION_ATTR_PUSH_MPLS, push_ethertype);
> +
> +            return n;
> +        }
> +    }
> +
> +    {
> +        ovs_be16 pop_ethertype;
> +        int etype;
> +        int n = -1;
> +
> +        if ((sscanf(s, "pop_mpls(eth_type=%i)%n", &etype, &n) > 0 && n > 0)) {
> +            pop_ethertype = htons(etype);
> +            nl_msg_put_be16(actions, OVS_ACTION_ATTR_POP_MPLS, pop_ethertype);
> +
> +            return n;
> +        }
> +    }
> +
> +    if (!strncmp(s, "dec_mpls_ttl", 12)) {
> +        nl_msg_put_flag(actions, OVS_ACTION_ATTR_DEC_MPLS_TTL);
> +        return 12;
> +    }
> +
> +    if (!strncmp(s, "copy_ttl_in", 11)) {
> +        nl_msg_put_flag(actions, OVS_ACTION_ATTR_COPY_TTL_IN);
> +        return 11;
> +    }
> +
> +    if (!strncmp(s, "copy_ttl_out", 12)) {
> +        nl_msg_put_flag(actions, OVS_ACTION_ATTR_COPY_TTL_OUT);
> +        return 12;
> +    }
> +
> +    {
>          double percentage;
>          int n = -1;
>  
> @@ -614,6 +724,7 @@ odp_flow_key_attr_len(uint16_t type)
>      case OVS_KEY_ATTR_ICMPV6: return sizeof(struct ovs_key_icmpv6);
>      case OVS_KEY_ATTR_ARP: return sizeof(struct ovs_key_arp);
>      case OVS_KEY_ATTR_ND: return sizeof(struct ovs_key_nd);
> +    case OVS_KEY_ATTR_MPLS: return sizeof(ovs_be32);
>  
>      case OVS_KEY_ATTR_UNSPEC:
>      case __OVS_KEY_ATTR_MAX:
> @@ -715,6 +826,12 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds)
>          ds_put_char(ds, ')');
>          break;
>  
> +    case OVS_KEY_ATTR_MPLS:
> +        ds_put_char(ds, '(');
> +        format_mpls_lse(ds, nl_attr_get_be32(a));
> +        ds_put_char(ds, ')');
> +        break;
> +
>      case OVS_KEY_ATTR_ETHERTYPE:
>          ds_put_format(ds, "(0x%04"PRIx16")",
>                        ntohs(nl_attr_get_be16(a)));
> @@ -984,6 +1101,21 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
>      }
>  
>      {
> +        int mpls_label, mpls_tc, mpls_ttl, mpls_stack;
> +        ovs_be32 mpls_lse;
> +        int n = -1;
> +
> +        if (sscanf(s, "mpls(label=%"SCNi32",tc=%i,ttl=%i,bos=%i)%n",
> +                    &mpls_label, &mpls_tc, &mpls_ttl, &mpls_stack, &n) > 0 &&
> +                    n > 0) {
> +            mpls_lse = format_mpls_lse_values(mpls_label, mpls_tc,
> +                                              mpls_ttl, mpls_stack);
> +            nl_msg_put_be32(key, OVS_KEY_ATTR_MPLS, mpls_lse);
> +            return n;
> +        }
> +    }
> +
> +    {
>          ovs_be32 ipv4_src;
>          ovs_be32 ipv4_dst;
>          int ipv4_proto;
> @@ -1291,6 +1423,11 @@ odp_flow_key_from_flow(struct ofpbuf *buf, const struct flow *flow)
>  
>      nl_msg_put_be16(buf, OVS_KEY_ATTR_ETHERTYPE, flow->dl_type);
>  
> +    if (flow->dl_type == htons(ETH_TYPE_MPLS) ||
> +        flow->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
> +        nl_msg_put_be32(buf, OVS_KEY_ATTR_MPLS, flow->mpls_lse);
> +    }
> +
>      if (flow->dl_type == htons(ETH_TYPE_IP)) {
>          struct ovs_key_ipv4 *ipv4_key;
>  
> @@ -1531,6 +1668,38 @@ parse_ethertype(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
>      return true;
>  }
>  
> +/* Parse MPLS header attributes. */
> +static enum odp_key_fitness
> +parse_mpls_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
> +                   uint64_t present_attrs, int out_of_range_attr,
> +                   uint64_t expected_attrs, struct flow *flow,
> +                   const struct nlattr *key, size_t key_len)
> +{
> +    enum odp_key_fitness fitness;
> +    ovs_be32 mpls_lse;
> +
> +    /* Calulate fitness of outer attributes. */
> +    expected_attrs |= (UINT64_C(1) << OVS_KEY_ATTR_MPLS);
> +    fitness = check_expectations(present_attrs, out_of_range_attr,
> +                                 expected_attrs, key, key_len);
> +
> +    /* Get the MPLS LSE value. */
> +    if (!(present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_MPLS))) {
> +        return ODP_FIT_TOO_LITTLE;
> +    }
> +    mpls_lse = nl_attr_get_be32(attrs[OVS_KEY_ATTR_MPLS]);
> +    if (mpls_lse == htonl(0)) {
> +        /* Corner case for a truncated MPLS header. */
> +        return fitness;
> +    }
> +
> +    /* Set mpls_lse. */
> +    flow->mpls_lse = mpls_lse;
> +
> +    return check_expectations(present_attrs, out_of_range_attr, expected_attrs,
> +                              key, key_len);
> +}
> +
>  static enum odp_key_fitness
>  parse_l3_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
>                  uint64_t present_attrs, int out_of_range_attr,
> @@ -1539,7 +1708,13 @@ parse_l3_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
>  {
>      static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
>  
> -    if (flow->dl_type == htons(ETH_TYPE_IP)) {
> +    /* Parse MPLS label stack entry */
> +    if (flow->dl_type == htons(ETH_TYPE_MPLS) ||
> +        flow->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
> +
> +        return parse_mpls_onward(attrs, present_attrs, out_of_range_attr,
> +                                  expected_attrs, flow, key, key_len);
> +    } else if (flow->dl_type == htons(ETH_TYPE_IP)) {
>          expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_IPV4;
>          if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_IPV4)) {
>              const struct ovs_key_ipv4 *ipv4_key;
> @@ -1709,8 +1884,10 @@ parse_8021q_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
>      if (!parse_ethertype(attrs, present_attrs, &expected_attrs, flow)) {
>          return ODP_FIT_ERROR;
>      }
> -    encap_fitness = parse_l3_onward(attrs, present_attrs, out_of_range_attr,
> -                                    expected_attrs, flow, key, key_len);
> +
> +    encap_fitness = parse_l3_onward(attrs, present_attrs,
> +                                    out_of_range_attr, expected_attrs,
> +                                    flow, key, key_len);
>  
>      /* The overall fitness is the worse of the outer and inner attributes. */
>      return MAX(fitness, encap_fitness);
> @@ -1789,6 +1966,7 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
>          return parse_8021q_onward(attrs, present_attrs, out_of_range_attr,
>                                    expected_attrs, flow, key, key_len);
>      }
> +
>      return parse_l3_onward(attrs, present_attrs, out_of_range_attr,
>                             expected_attrs, flow, key, key_len);
>  }
> @@ -1956,6 +2134,63 @@ commit_set_ipv6_action(const struct flow *flow, struct flow *base,
>                        &ipv6_key, sizeof(ipv6_key));
>  }
>  
> +/* Handle MPLS Push action. Update flow based on incoming packet. */
> +void
> +commit_mpls_push_action(struct flow *flow, struct flow *base,
> +                        struct ofpbuf *odp_actions, ovs_be16 eth_type)
> +{
> +    ovs_be32 mpls_label, mpls_tc, mpls_ttl, mpls_stack;
> +    assert(eth_type == htons(ETH_TYPE_MPLS) ||
> +           eth_type == htons(ETH_TYPE_MPLS_MCAST));
> +
> +    if (base->mpls_lse != htonl(0)) {
> +        flow->mpls_lse = base->mpls_lse;
> +        flow->mpls_lse &= ~htonl(MPLS_STACK_MASK);
> +    } else {
> +        flow->mpls_lse &= ~htonl(MPLS_LABEL_MASK | MPLS_TC_MASK |
> +                                 MPLS_TTL_MASK | MPLS_STACK_MASK);
> +        if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
> +            mpls_label = htonl(0x2); /* IPV6 Explicit Null. */
> +        } else {
> +             mpls_label = htonl(0x0); /* IPV4 Explicit Null. */
> +        }
> +        mpls_tc = htonl(((flow->nw_tos & IP_DSCP_MASK) >> 2) << MPLS_TC_SHIFT);
> +        mpls_ttl = htonl(flow->nw_ttl << MPLS_TTL_SHIFT);
> +        if (mpls_ttl == htonl(0)) {
> +            mpls_ttl = htonl(0x40); /* Set default ttl for non-IP. */
> +        }
> +        mpls_stack = htonl(0x1 << MPLS_STACK_SHIFT);
> +        flow->mpls_lse = mpls_label | mpls_tc | mpls_ttl | mpls_stack;
> +    }
> +
> +    nl_msg_put_be16(odp_actions, OVS_ACTION_ATTR_PUSH_MPLS, eth_type);
> +    base->dl_type = flow->dl_type = eth_type;
> +}
> +
> +/* Handle MPLS Pop action. Update packet flow. */
> +void
> +commit_mpls_pop_action(const struct flow *flow, struct flow *base,
> +                       struct ofpbuf *odp_actions)
> +{
> +    assert(!(flow->dl_type == htons(ETH_TYPE_MPLS) ||
> +             flow->dl_type == htons(ETH_TYPE_MPLS_MCAST)));
> +    nl_msg_put_be16(odp_actions, OVS_ACTION_ATTR_POP_MPLS, flow->dl_type);
> +    base->dl_type = flow->dl_type;
> +}
> +
> +/* Handle MPLS Label Stack Entry action. Update packet flow. */
> +void
> +commit_mpls_lse_action(const struct flow *flow, struct flow *base,
> +                         struct ofpbuf *odp_actions)
> +{
> +    if (base->mpls_lse == flow->mpls_lse) {
> +        return;
> +    }
> +
> +    nl_msg_put_be32(odp_actions, OVS_ACTION_ATTR_SET_MPLS_LSE, flow->mpls_lse);
> +    base->mpls_lse = flow->mpls_lse;
> +}
> +
>  static void
>  commit_set_nw_action(const struct flow *flow, struct flow *base,
>                       struct ofpbuf *odp_actions)
> diff --git a/lib/odp-util.h b/lib/odp-util.h
> index f902b76..db85c28 100644
> --- a/lib/odp-util.h
> +++ b/lib/odp-util.h
> @@ -131,6 +131,12 @@ const char *odp_key_fitness_to_string(enum odp_key_fitness);
>  
>  void commit_odp_actions(const struct flow *, struct flow *base,
>                          struct ofpbuf *odp_actions);
> +void commit_mpls_push_action(struct flow *flow, struct flow *base,
> +                             struct ofpbuf *odp_actions, ovs_be16 eth_type);
> +void commit_mpls_pop_action(const struct flow *flow, struct flow *base,
> +                             struct ofpbuf *odp_actions);
> +void commit_mpls_lse_action(const struct flow *flow, struct flow *base,
> +                              struct ofpbuf *odp_actions);
>  
>  /* ofproto-dpif interface.
>   *
> diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
> index 73a70c6..7fcaf13 100644
> --- a/lib/ofp-parse.c
> +++ b/lib/ofp-parse.c
> @@ -321,6 +321,11 @@ parse_named_action(enum ofputil_action_code code, const struct flow *flow,
>      struct ofp_action_vlan_vid *oavv;
>      struct ofp_action_nw_addr *oana;
>      struct ofp_action_tp_port *oata;
> +    struct nx_action_mpls_label *naml;
> +    struct nx_action_mpls_tc *namtc;
> +    struct nx_action_mpls_ttl *namttl;
> +    struct nx_action_push_mpls *nampush;
> +    struct nx_action_pop_mpls *nampop;
>  
>      switch (code) {
>      case OFPUTIL_OFPAT10_OUTPUT:
> @@ -423,6 +428,43 @@ parse_named_action(enum ofputil_action_code code, const struct flow *flow,
>          learn_parse(b, arg, flow);
>          break;
>  
> +    case OFPUTIL_NXAST_COPY_TTL_OUT:
> +        ofputil_put_NXAST_COPY_TTL_OUT(b);
> +        break;
> +
> +    case OFPUTIL_NXAST_COPY_TTL_IN:
> +        ofputil_put_NXAST_COPY_TTL_IN(b);
> +        break;
> +
> +    case OFPUTIL_NXAST_SET_MPLS_LABEL:
> +        naml = ofputil_put_NXAST_SET_MPLS_LABEL(b);
> +        naml->mpls_label = htonl(str_to_u32(arg));
> +        break;
> +
> +    case OFPUTIL_NXAST_SET_MPLS_TC:
> +        namtc = ofputil_put_NXAST_SET_MPLS_TC(b);
> +        namtc->mpls_tc = str_to_u32(arg);
> +        break;
> +
> +    case OFPUTIL_NXAST_SET_MPLS_TTL:
> +        namttl = ofputil_put_NXAST_SET_MPLS_TTL(b);
> +        namttl->mpls_ttl = str_to_u32(arg);
> +        break;
> +
> +    case OFPUTIL_NXAST_DEC_MPLS_TTL:
> +        ofputil_put_NXAST_DEC_MPLS_TTL(b);
> +        break;
> +
> +    case OFPUTIL_NXAST_PUSH_MPLS:
> +        nampush = ofputil_put_NXAST_PUSH_MPLS(b);
> +        nampush->ethertype = htons(str_to_u16(arg, "push_mpls"));
> +        break;
> +
> +    case OFPUTIL_NXAST_POP_MPLS:
> +        nampop = ofputil_put_NXAST_POP_MPLS(b);
> +        nampop->ethertype = htons(str_to_u16(arg, "pop_mpls"));
> +        break;
> +
>      case OFPUTIL_NXAST_EXIT:
>          ofputil_put_NXAST_EXIT(b);
>          break;
> @@ -496,6 +538,8 @@ parse_protocol(const char *name, const struct protocol **p_out)
>          { "icmp6", ETH_TYPE_IPV6, IPPROTO_ICMPV6 },
>          { "tcp6", ETH_TYPE_IPV6, IPPROTO_TCP },
>          { "udp6", ETH_TYPE_IPV6, IPPROTO_UDP },
> +        { "mpls", ETH_TYPE_MPLS, 0 },
> +        { "mplsm", ETH_TYPE_MPLS_MCAST, 0 },
>      };
>      const struct protocol *p;
>  
> diff --git a/lib/ofp-print.c b/lib/ofp-print.c
> index 9d4396c..8ab662c 100644
> --- a/lib/ofp-print.c
> +++ b/lib/ofp-print.c
> @@ -168,6 +168,11 @@ ofp_print_action(struct ds *s, const union ofp_action *a,
>  {
>      const struct ofp_action_enqueue *oae;
>      const struct ofp_action_dl_addr *oada;
> +    const struct nx_action_mpls_label *naml;
> +    const struct nx_action_push_mpls *nampush;
> +    const struct nx_action_pop_mpls  *nampop;
> +    const struct nx_action_mpls_tc  *namtc;
> +    const struct nx_action_mpls_ttl  *namttl;
>      const struct nx_action_set_tunnel64 *nast64;
>      const struct nx_action_set_tunnel *nast;
>      const struct nx_action_set_queue *nasq;
> @@ -337,6 +342,43 @@ ofp_print_action(struct ds *s, const union ofp_action *a,
>          ds_put_cstr(s, "dec_ttl");
>          break;
>  
> +    case OFPUTIL_NXAST_SET_MPLS_LABEL:
> +        naml = (const struct nx_action_mpls_label *) a;
> +        ds_put_format(s, "set_mpls_label:%"PRIu32, ntohl(naml->mpls_label));
> +        break;
> +
> +    case OFPUTIL_NXAST_SET_MPLS_TC:
> +        namtc = (const struct nx_action_mpls_tc *) a;
> +        ds_put_format(s, "set_mpls_tc:%"PRIu8, namtc->mpls_tc);
> +        break;
> +
> +    case OFPUTIL_NXAST_SET_MPLS_TTL:
> +        namttl = (const struct nx_action_mpls_ttl *) a;
> +        ds_put_format(s, "set_mpls_ttl:%"PRIu8, namttl->mpls_ttl);
> +        break;
> +
> +    case OFPUTIL_NXAST_DEC_MPLS_TTL:
> +        ds_put_cstr(s, "dec_mpls_ttl");
> +        break;
> +
> +    case OFPUTIL_NXAST_COPY_TTL_IN:
> +        ds_put_cstr(s, "copy_ttl_in");
> +        break;
> +
> +    case OFPUTIL_NXAST_COPY_TTL_OUT:
> +        ds_put_cstr(s, "copy_ttl_out");
> +        break;
> +
> +   case OFPUTIL_NXAST_PUSH_MPLS:
> +        nampush = (const struct nx_action_push_mpls *) a;
> +        ds_put_format(s, "push_mpls:0x%"PRIx16, ntohs(nampush->ethertype));
> +        break;
> +
> +    case OFPUTIL_NXAST_POP_MPLS:
> +        nampop = (const struct nx_action_pop_mpls *) a;
> +        ds_put_format(s, "pop_mpls:0x%"PRIx16, ntohs(nampop->ethertype));
> +        break;
> +
>      case OFPUTIL_NXAST_EXIT:
>          ds_put_cstr(s, "exit");
>          break;
> @@ -861,6 +903,10 @@ ofp_match_to_string(const struct ofp_match *om, int verbosity)
>              }
>          } else if (om->dl_type == htons(ETH_TYPE_ARP)) {
>              ds_put_cstr(&f, "arp,");
> +        } else if (om->dl_type == htons(ETH_TYPE_MPLS)) {
> +            ds_put_cstr(&f, "mpls,");
> +        } else if (om->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
> +            ds_put_cstr(&f, "mplsm,");
>          } else {
>              skip_type = false;
>          }
> diff --git a/lib/ofp-util.c b/lib/ofp-util.c
> index e5f43db..c61efda 100644
> --- a/lib/ofp-util.c
> +++ b/lib/ofp-util.c
> @@ -99,7 +99,7 @@ static const flow_wildcards_t WC_INVARIANTS = 0
>  void
>  ofputil_wildcard_from_openflow(uint32_t ofpfw, struct flow_wildcards *wc)
>  {
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
>  
>      /* Initialize most of rule->wc. */
>      flow_wildcards_init_catchall(wc);
> @@ -107,7 +107,8 @@ ofputil_wildcard_from_openflow(uint32_t ofpfw, struct flow_wildcards *wc)
>  
>      /* Wildcard fields that aren't defined by ofp_match or tun_id. */
>      wc->wildcards |= (FWW_ARP_SHA | FWW_ARP_THA | FWW_NW_ECN | FWW_NW_TTL
> -                      | FWW_IPV6_LABEL);
> +                      | FWW_IPV6_LABEL | FWW_MPLS_LABEL | FWW_MPLS_TC
> +                      | FWW_MPLS_STACK);
>  
>      if (ofpfw & OFPFW_NW_TOS) {
>          /* OpenFlow 1.0 defines a TOS wildcard, but it's much later in
> @@ -1178,7 +1179,7 @@ ofputil_usable_protocols(const struct cls_rule *rule)
>  {
>      const struct flow_wildcards *wc = &rule->wc;
>  
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
>  
>      /* NXM and OF1.1+ supports bitwise matching on ethernet addresses. */
>      if (!eth_mask_is_exact(wc->dl_src_mask)
> @@ -1237,6 +1238,21 @@ ofputil_usable_protocols(const struct cls_rule *rule)
>          return OFPUTIL_P_NXM_ANY;
>      }
>  
> +    /* Only NXM supports matching mpls label */
> +    if (!(wc->wildcards & FWW_MPLS_LABEL)) {
> +        return OFPUTIL_P_NXM_ANY;
> +    }
> +
> +    /* Only NXM supports matching mpls tc */
> +    if (!(wc->wildcards & FWW_MPLS_TC)) {
> +        return OFPUTIL_P_NXM_ANY;
> +    }
> +
> +    /* Only NXM supports matching mpls stack */
> +    if (!(wc->wildcards & FWW_MPLS_STACK)) {
> +        return OFPUTIL_P_NXM_ANY;
> +    }
> +
>      /* Other formats can express this rule. */
>      return OFPUTIL_P_ANY;
>  }
> @@ -3444,6 +3460,9 @@ validate_actions(const union ofp_action *actions, size_t n_actions,
>          enum ofperr error;
>          uint16_t port;
>          int code;
> +        ovs_be16 etype;
> +        ovs_be32 mpls_label;
> +        uint8_t mpls_tc, mpls_ttl;
>  
>          code = ofputil_decode_action(a);
>          if (code < 0) {
> @@ -3474,6 +3493,43 @@ validate_actions(const union ofp_action *actions, size_t n_actions,
>              }
>              break;
>  
> +        case OFPUTIL_NXAST_PUSH_MPLS:
> +            etype = ((const struct nx_action_push_mpls *) a)->ethertype;
> +            if (etype != htons(ETH_TYPE_MPLS) &&
> +                etype != htons(ETH_TYPE_MPLS_MCAST)) {
> +                error = OFPERR_OFPBAC_BAD_ARGUMENT;
> +            }
> +            break;
> +
> +        case OFPUTIL_NXAST_POP_MPLS:
> +            etype = ((const struct nx_action_pop_mpls *) a)->ethertype;
> +            if (etype == htons(ETH_TYPE_MPLS) ||
> +                etype == htons(ETH_TYPE_MPLS_MCAST)) {
> +                error = OFPERR_OFPBAC_BAD_ARGUMENT;
> +            }
> +            break;
> +
> +        case OFPUTIL_NXAST_SET_MPLS_LABEL:
> +            mpls_label = ((const struct nx_action_mpls_label *) a)->mpls_label;
> +            if (mpls_label & ~htonl(0x000fffff)) {
> +                error = OFPERR_OFPBAC_BAD_ARGUMENT;
> +            }
> +            break;
> +
> +        case OFPUTIL_NXAST_SET_MPLS_TC:
> +            mpls_tc = ((const struct nx_action_mpls_tc *) a)->mpls_tc;
> +            if (mpls_tc & ~7) {
> +                error = OFPERR_OFPBAC_BAD_ARGUMENT;
> +            }
> +            break;
> +
> +        case OFPUTIL_NXAST_SET_MPLS_TTL:
> +            mpls_ttl = ((const struct nx_action_mpls_ttl *) a)->mpls_ttl;
> +            if (mpls_ttl == 0 || mpls_ttl == 1) {
> +                error = OFPERR_OFPBAC_BAD_ARGUMENT;
> +            }
> +            break;
> +
>          case OFPUTIL_OFPAT10_ENQUEUE:
>              port = ntohs(((const struct ofp_action_enqueue *) a)->port);
>              if (port >= max_ports && port != OFPP_IN_PORT
> @@ -3545,6 +3601,9 @@ validate_actions(const union ofp_action *actions, size_t n_actions,
>          case OFPUTIL_NXAST_EXIT:
>          case OFPUTIL_NXAST_DEC_TTL:
>          case OFPUTIL_NXAST_FIN_TIMEOUT:
> +        case OFPUTIL_NXAST_COPY_TTL_OUT:
> +        case OFPUTIL_NXAST_COPY_TTL_IN:
> +        case OFPUTIL_NXAST_DEC_MPLS_TTL:
>              break;
>          }
>  
> @@ -3798,7 +3857,8 @@ ofputil_normalize_rule(struct cls_rule *rule)
>          MAY_ARP_SHA     = 1 << 4, /* arp_sha */
>          MAY_ARP_THA     = 1 << 5, /* arp_tha */
>          MAY_IPV6        = 1 << 6, /* ipv6_src, ipv6_dst, ipv6_label */
> -        MAY_ND_TARGET   = 1 << 7  /* nd_target */
> +        MAY_ND_TARGET   = 1 << 7, /* nd_target */
> +        MAY_MPLS        = 1 << 8, /* mpls label and tc */
>      } may_match;
>  
>      struct flow_wildcards wc;
> @@ -3826,6 +3886,9 @@ ofputil_normalize_rule(struct cls_rule *rule)
>          }
>      } else if (rule->flow.dl_type == htons(ETH_TYPE_ARP)) {
>          may_match = MAY_NW_PROTO | MAY_NW_ADDR | MAY_ARP_SHA | MAY_ARP_THA;
> +    } else if (rule->flow.dl_type == htons(ETH_TYPE_MPLS) ||
> +               rule->flow.dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
> +        may_match = MAY_MPLS;
>      } else {
>          may_match = 0;
>      }
> @@ -3859,6 +3922,11 @@ ofputil_normalize_rule(struct cls_rule *rule)
>      if (!(may_match & MAY_ND_TARGET)) {
>          wc.nd_target_mask = in6addr_any;
>      }
> +    if (!(may_match & MAY_MPLS)) {
> +        wc.wildcards |= FWW_MPLS_LABEL;
> +        wc.wildcards |= FWW_MPLS_TC;
> +        wc.wildcards |= FWW_MPLS_STACK;
> +    }
>  
>      /* Log any changes. */
>      if (!flow_wildcards_equal(&wc, &rule->wc)) {
> diff --git a/lib/ofp-util.def b/lib/ofp-util.def
> index 8739ac0..0380a84 100644
> --- a/lib/ofp-util.def
> +++ b/lib/ofp-util.def
> @@ -39,4 +39,12 @@ NXAST_ACTION(NXAST_EXIT,           nx_action_header,       0, "exit")
>  NXAST_ACTION(NXAST_DEC_TTL,        nx_action_header,       0, "dec_ttl")
>  NXAST_ACTION(NXAST_FIN_TIMEOUT,    nx_action_fin_timeout,  0, "fin_timeout")
>  NXAST_ACTION(NXAST_CONTROLLER,     nx_action_controller,   0, "controller")
> +NXAST_ACTION(NXAST_COPY_TTL_OUT,   nx_action_header,       0, "copy_ttl_out")
> +NXAST_ACTION(NXAST_COPY_TTL_IN,    nx_action_header,       0, "copy_ttl_in")
> +NXAST_ACTION(NXAST_SET_MPLS_LABEL, nx_action_mpls_label,   0, "set_mpls_label")
> +NXAST_ACTION(NXAST_SET_MPLS_TC,    nx_action_mpls_tc,      0, "set_mpls_tc")
> +NXAST_ACTION(NXAST_SET_MPLS_TTL,   nx_action_mpls_ttl,     0, "set_mpls_ttl")
> +NXAST_ACTION(NXAST_DEC_MPLS_TTL,   nx_action_header,       0, "dec_mpls_ttl")
> +NXAST_ACTION(NXAST_PUSH_MPLS,      nx_action_push_mpls,    0, "push_mpls")
> +NXAST_ACTION(NXAST_POP_MPLS,       nx_action_pop_mpls,     0, "pop_mpls")
>  #undef NXAST_ACTION
> diff --git a/lib/ofpbuf.c b/lib/ofpbuf.c
> index 02e5aa8..a850d1b 100644
> --- a/lib/ofpbuf.c
> +++ b/lib/ofpbuf.c
> @@ -30,7 +30,7 @@ ofpbuf_use__(struct ofpbuf *b, void *base, size_t allocated,
>      b->allocated = allocated;
>      b->source = source;
>      b->size = 0;
> -    b->l2 = b->l3 = b->l4 = b->l7 = NULL;
> +    b->l2 = b->l2_5 = b->l3 = b->l4 = b->l7 = NULL;
>      list_poison(&b->list_node);
>      b->private_p = NULL;
>  }
> @@ -177,6 +177,9 @@ ofpbuf_clone_with_headroom(const struct ofpbuf *buffer, size_t headroom)
>      if (buffer->l2) {
>          new_buffer->l2 = (char *) buffer->l2 + data_delta;
>      }
> +    if (buffer->l2_5) {
> +        new_buffer->l2_5 = (char *) buffer->l2_5 + data_delta;
> +    }
>      if (buffer->l3) {
>          new_buffer->l3 = (char *) buffer->l3 + data_delta;
>      }
> @@ -296,6 +299,9 @@ ofpbuf_resize__(struct ofpbuf *b, size_t new_headroom, size_t new_tailroom)
>          if (b->l2) {
>              b->l2 = (char *) b->l2 + data_delta;
>          }
> +        if (b->l2_5) {
> +            b->l2_5 = (char *) b->l2_5 + data_delta;
> +        }
>          if (b->l3) {
>              b->l3 = (char *) b->l3 + data_delta;
>          }
> diff --git a/lib/ofpbuf.h b/lib/ofpbuf.h
> index 520455d..bae3c0a 100644
> --- a/lib/ofpbuf.h
> +++ b/lib/ofpbuf.h
> @@ -43,6 +43,7 @@ struct ofpbuf {
>      size_t size;                /* Number of bytes in use. */
>  
>      void *l2;                   /* Link-level header. */
> +    void *l2_5;                 /* MPLS label stack */
>      void *l3;                   /* Network-level header. */
>      void *l4;                   /* Transport-level header. */
>      void *l7;                   /* Application data. */
> diff --git a/lib/packets.c b/lib/packets.c
> index bbf4934..ec7a35e 100644
> --- a/lib/packets.c
> +++ b/lib/packets.c
> @@ -20,6 +20,7 @@
>  #include <arpa/inet.h>
>  #include <sys/socket.h>
>  #include <netinet/in.h>
> +#include <netinet/ip6.h>
>  #include <stdlib.h>
>  #include "byte-order.h"
>  #include "csum.h"
> @@ -198,6 +199,357 @@ eth_pop_vlan(struct ofpbuf *packet)
>      }
>  }
>  
> +/* Set ethertype of the packet. */
> +static void
> +set_ethertype(struct ofpbuf *packet, ovs_be16 eth_type)
> +{
> +    struct eth_header *eh = packet->data;
> +
> +    if (eh->eth_type == htons(ETH_TYPE_VLAN)) {
> +        /* ethtype for VLAN packets is at L3_offset - 2 bytes. */
> +        ovs_be16 *next_ethtype;
> +        next_ethtype = (ovs_be16 *)((char *)packet->l3 - 2);
> +        *next_ethtype = eth_type;
> +    } else {
> +        eh->eth_type = eth_type;
> +    }
> +}
> +
> +/* Get ethertype of the packet. */
> +static ovs_be16
> +get_ethertype(struct ofpbuf *packet)
> +{
> +    struct eth_header *eh = packet->data;
> +    char *mh = packet->l2_5;
> +    ovs_be16 *ethtype = NULL;
> +
> +    if (eh->eth_type == htons(ETH_TYPE_VLAN)) {
> +        if (mh != NULL) {
> +            ethtype = (ovs_be16 *)(mh - 2);
> +        } else {
> +            ethtype = (ovs_be16 *)((char *)packet->l3 - 2);
> +        }
> +        return *ethtype;
> +    } else {
> +        return eh->eth_type;
> +    }
> +}
> +
> +/* Extract ttl and tos from ipv4 or ipv6 header
> +   for non-IP pick default value. */
> +static int
> +get_label_ttl_and_tos(struct ofpbuf* packet, uint8_t *ttl,
> +                      uint8_t *tos, uint8_t *label)
> +{
> +    struct eth_header *eh = packet->data;
> +    struct ip_header *ih = packet->l3;
> +    struct ip6_hdr   *ih6 = packet->l3;
> +    struct mpls_hdr *mh = packet->l2_5;
> +    ovs_be16 ethtype = htons(0);
> +
> +    if (packet->size < sizeof *eh + sizeof *ih) {
> +        return 0;
> +    }
> +
> +    ethtype = get_ethertype(packet);
> +
> +    switch (ntohs(ethtype)) {
> +
> +    case ETH_TYPE_IP:
> +        *ttl = ih->ip_ttl;
> +        *tos = IP_DSCP(ih->ip_tos) & 0x07;
> +        *label = 0; /* IPV4 Explicit Null label. */
> +	break;
> +
> +    case ETH_TYPE_IPV6:
> +        *ttl = ih6->ip6_hlim;
> +        *tos = IP6_TC(ntohl(ih6->ip6_flow)) & 0x07;
> +        *label = 2; /* IPV6 Explicit Null label. */
> +	break;
> +
> +    case ETH_TYPE_MPLS:
> +    case ETH_TYPE_MPLS_MCAST:
> +        *ttl = mpls_lse_to_ttl(mh->mpls_lse);
> +        *tos = mpls_lse_to_tc(mh->mpls_lse);
> +        *label = mpls_lse_to_label(mh->mpls_lse);
> +	break;
> +
> +    default:
> +        *ttl = 64;
> +        *tos = 0;
> +        *label = 0; /* default label. */
> +	break;
> +    }
> +    return 1;
> +}
> +
> +/* Set MPLS tag time-to-live. */
> +static void
> +set_mpls_lse_ttl(ovs_be32 *tag, ovs_be32 ttl)
> +{
> +    *tag &= ~htonl(MPLS_TTL_MASK);
> +    *tag |= ttl & htonl(MPLS_TTL_MASK);
> +}
> +
> +/* Set MPLS tag traffic-class. */
> +static void
> +set_mpls_lse_tc(ovs_be32 *tag, ovs_be32 tc)
> +{
> +    *tag &= ~htonl(MPLS_TC_MASK);
> +    *tag |= tc & htonl(MPLS_TC_MASK);
> +}
> +
> +/* Set MPLS tag label. */
> +static void
> +set_mpls_lse_label(ovs_be32 *tag, ovs_be32 label)
> +{
> +    *tag &= ~htonl(MPLS_LABEL_MASK);
> +    *tag |= label & htonl(MPLS_LABEL_MASK);
> +}
> +
> +/* Set MPLS tag stack. */
> +static void
> +set_mpls_lse_stack(ovs_be32 *tag, ovs_be32 stack)
> +{
> +    *tag &= ~htonl(MPLS_STACK_MASK);
> +    *tag |= stack & htonl(MPLS_STACK_MASK);
> +}
> +
> +/* Set MPLS lse from actions. */
> +static void
> +set_new_mpls_lse(struct mpls_hdr *mh, ovs_be32 mpls_lse)
> +{
> +    set_mpls_lse_label(&mh->mpls_lse, mpls_lse);
> +    set_mpls_lse_ttl(&mh->mpls_lse, mpls_lse);
> +    set_mpls_lse_tc(&mh->mpls_lse, mpls_lse);
> +}
> +
> +/* Set MPLS label, MPLS TC, MPLS ttl and MPLS stack. */
> +static void
> +set_mpls_lse_values(ovs_be32 *tag, uint8_t ttl, uint8_t stack,
> +                    uint8_t tc, uint32_t label)
> +{
> +    set_mpls_lse_ttl(tag, htonl(ttl << MPLS_TTL_SHIFT));
> +    set_mpls_lse_tc(tag, htonl(tc << MPLS_TC_SHIFT));
> +    set_mpls_lse_label(tag, htonl(label << MPLS_LABEL_SHIFT));
> +    set_mpls_lse_stack(tag, htonl(stack << MPLS_STACK_SHIFT));
> +}
> +
> +/* Adjust L2 and L2.5 data after pushing new mpls shim header. */
> +static void
> +push_mpls_lse(struct ofpbuf *packet, struct mpls_hdr *mh)
> +{
> +    char * header;
> +    size_t len;
> +    header = ofpbuf_push_uninit(packet, MPLS_HLEN);
> +    len = (char*)packet->l2_5 - (char*)packet->l2;
> +    memmove(header, packet->l2, len);
> +    memcpy((char*)header + len, mh, sizeof *mh);
> +    packet->l2 = (char*)packet->l2 - MPLS_HLEN;
> +    packet->l2_5 = (char*)packet->l2_5 - MPLS_HLEN;
> +}
> +
> +/* Decrement MPLS TTL from the packet.
> + * 'packet->l2_5' must initially point to 'packet''s MPLS Label stack. */
> +void
> +dec_mpls_ttl(struct ofpbuf *packet)
> +{
> +    ovs_be16 eth_type = htons(0);
> +    struct eth_header *eh = packet->data;
> +    struct mpls_hdr *mh = packet->l2_5;
> +
> +    if (packet->size < sizeof *eh) {
> +        return;
> +    }
> +
> +    /* Packet type should be mpls to decrement ttl. */
> +    eth_type = get_ethertype(packet);
> +
> +    if (eth_type == htons(ETH_TYPE_MPLS) ||
> +        eth_type == htons(ETH_TYPE_MPLS_MCAST)) {
> +
> +        uint8_t ttl = mpls_lse_to_ttl(mh->mpls_lse);
> +        if (ttl > 1) {
> +            set_mpls_lse_ttl(&mh->mpls_lse, htonl(--ttl << MPLS_TTL_SHIFT));
> +        }
> +    }
> +}
> +
> +/* Copy MPLS TTL from the packet either ipv4/ipv6.
> + * 'packet->l2_5' must initially point to 'packet''s MPLS Label stack. */
> +void
> +copy_mpls_ttl_in(struct ofpbuf *packet)
> +{
> +    uint8_t ttl;
> +    struct eth_header *eh = packet->data;
> +    struct mpls_hdr *mh = packet->l2_5;
> +    struct ip_header *ih = packet->l3;
> +    struct ip6_hdr *ih6 = packet->l3;
> +    ovs_be16 eth_type = htons(0);
> +    size_t hdr_size = sizeof *eh + sizeof *mh + sizeof *ih;
> +
> +    if (packet->size < hdr_size) {
> +        return;
> +    }
> +
> +    /* Packet type should be mpls to copy ttl to l3. */
> +    eth_type = get_ethertype(packet);
> +    if (eth_type == htons(ETH_TYPE_MPLS) ||
> +        eth_type == htons(ETH_TYPE_MPLS_MCAST)) {
> +
> +        ttl = mpls_lse_to_ttl(mh->mpls_lse);
> +        /* If bottom of the stack handle IP checksum. */
> +        if (mh->mpls_lse & htonl(MPLS_STACK_MASK)) {
> +            if (IP_VER(ih->ip_ihl_ver) == IP_VERSION) {
> +                /* Change the ip checksum. */
> +                uint8_t *field = &ih->ip_ttl;
> +                ih->ip_csum = recalc_csum16(ih->ip_csum,
> +                                 htons(*field << 8), htons(ttl << 8));
> +                ih->ip_ttl = ttl;
> +            } else if (IP6_VER(ih6->ip6_vfc) == IP6_VERSION) {
> +                ih6->ip6_hlim = ttl;
> +            }
> +        } else {
> +            struct mpls_hdr *mh2;
> +            mh2 = (struct mpls_hdr *)((char *) packet->l2_5 + sizeof *mh);
> +            set_mpls_lse_ttl(&mh2->mpls_lse, htonl(ttl << MPLS_TTL_SHIFT));
> +        }
> +    }
> +}
> +
> +/* Copy MPLS TTL to the packet layer3 only ipv4/ipv6.
> + * 'packet->l2_5' must initially point to 'packet''s MPLS Label stack. */
> +void
> +copy_mpls_ttl_out(struct ofpbuf *packet)
> +{
> +    struct eth_header *eh = packet->data;
> +    struct mpls_hdr *mh = packet->l2_5;
> +    struct ip_header *ih = packet->l3;
> +    struct ip6_hdr   *ih6 = packet->l3;
> +    ovs_be16 eth_type = htons(0);
> +    size_t hdr_size = sizeof *eh + sizeof *mh + sizeof *ih;
> +
> +    if (packet->size < hdr_size) {
> +        return;
> +    }
> +
> +    /* Packet type should me mpls to copy ttl from l3. */
> +    eth_type = get_ethertype(packet);
> +    if (eth_type == htons(ETH_TYPE_MPLS) ||
> +        eth_type == htons(ETH_TYPE_MPLS_MCAST)) {
> +
> +        /* If bottom of the stack copy from l3. */
> +        if (mh->mpls_lse & htonl(MPLS_STACK_MASK)) {
> +            uint8_t nh_ttl;
> +            /* Get ipv4 or ipv6 or default ttl. */
> +            if (IP_VER(ih->ip_ihl_ver) == IP_VERSION) {
> +                nh_ttl = ih->ip_ttl;
> +            } else if (IP6_VER(ih6->ip6_vfc) == IP6_VERSION) {
> +                nh_ttl = ih6->ip6_hlim;
> +            } else {
> +                nh_ttl = 64; /* Default ttl for non-IP. */
> +            }
> +            set_mpls_lse_ttl(&mh->mpls_lse, htonl(nh_ttl << MPLS_TTL_SHIFT));
> +        } else {
> +            struct mpls_hdr *mh2;
> +            uint8_t ttl;
> +            mh2 = (struct mpls_hdr *)((char *) packet->l2_5 + sizeof *mh);
> +            ttl = mpls_lse_to_ttl(mh2->mpls_lse);
> +            set_mpls_lse_ttl(&mh->mpls_lse, htonl(ttl << MPLS_TTL_SHIFT));
> +        }
> +    }
> +    return;
> +}
> +
> +/* Set MPLS label stack entry to outermost MPLS header.*/
> +void
> +set_mpls_lse(struct ofpbuf *packet, ovs_be32 mpls_lse)
> +{
> +    struct eth_header *eh = packet->data;
> +    struct mpls_hdr *mh = packet->l2_5;
> +    ovs_be16 eth_type = htons(0);
> +
> +    if (packet->size < sizeof *eh) {
> +        return;
> +    }
> +
> +    /* Packet type should me mpls to set label stack entry. */
> +    eth_type = get_ethertype(packet);
> +    if (eth_type == htons(ETH_TYPE_MPLS) ||
> +        eth_type == htons(ETH_TYPE_MPLS_MCAST)) {
> +        set_new_mpls_lse(mh, mpls_lse);
> +    }
> +}
> +
> +/* Push MPLS label stack entry onto packet. */
> +void
> +push_mpls(struct ofpbuf *packet, ovs_be16 ethtype)
> +{
> +    struct eth_header *eh = packet->data;
> +    uint8_t nh_ttl, nh_tos, label;
> +    ovs_be16 eth_type = htons(0);
> +
> +    if (packet->size < sizeof *eh ||
> +        (ethtype != htons(ETH_TYPE_MPLS) &&
> +         ethtype != htons(ETH_TYPE_MPLS_MCAST))) {
> +        return;
> +    }
> +
> +    /* Get the packet ether_type. */
> +    eth_type = get_ethertype(packet);
> +
> +    /* Get Label, time-to-live and tos from L3 or L2.5. */
> +    if (get_label_ttl_and_tos(packet, &nh_ttl, &nh_tos, &label)) {
> +        struct mpls_hdr mh;
> +
> +        if (eth_type == htons(ETH_TYPE_MPLS) ||
> +            eth_type == htons(ETH_TYPE_MPLS_MCAST)) {
> +            set_mpls_lse_values(&mh.mpls_lse, nh_ttl, 0, nh_tos, label);
> +        } else {
> +            /* Set ethtype and mpls label stack entry. */
> +            set_ethertype(packet, ethtype);
> +            set_mpls_lse_values(&mh.mpls_lse, nh_ttl, 1, nh_tos, label);
> +            packet->l2_5 = packet->l3;
> +        }
> +        /* Push new MPLS shim header onto packet. */
> +        push_mpls_lse(packet, &mh);
> +    }
> +}
> +
> +/* Pop outermost MPLS label stack entry from packet. */
> +void
> +pop_mpls(struct ofpbuf *packet, ovs_be16 ethtype)
> +{
> +    struct eth_header *eh = packet->data;
> +    struct mpls_hdr *mh = NULL;
> +    ovs_be16 eth_type = htons(0);
> +
> +    if (packet->size < sizeof *eh + sizeof *mh)
> +        return;
> +
> +    eth_type = get_ethertype(packet);
> +
> +    if (eth_type == htons(ETH_TYPE_MPLS) ||
> +        eth_type == htons(ETH_TYPE_MPLS_MCAST)) {
> +        size_t len;
> +        mh = packet->l2_5;
> +        len = (char*)packet->l2_5 - (char*)packet->l2;
> +        /* If bottom of the stack set ethertype. */
> +        if (mh->mpls_lse & htonl(MPLS_STACK_MASK)) {
> +            packet->l3 = packet->l2_5;
> +            packet->l2_5 = NULL;
> +            set_ethertype(packet, ethtype);
> +        } else {
> +            packet->l2_5 = (char*)packet->l2_5 + MPLS_HLEN;
> +        }
> +        /* Shift the l2 header forward. */
> +        memmove((char*)packet->data + MPLS_HLEN, packet->data, len);
> +        packet->size -= MPLS_HLEN;
> +        packet->data = (char*)packet->data + MPLS_HLEN;
> +        packet->l2 = (char*)packet->l2 + MPLS_HLEN;
> +    }
> +}
> +
>  /* Converts hex digits in 'hex' to an Ethernet packet in '*packetp'.  The
>   * caller must free '*packetp'.  On success, returns NULL.  On failure, returns
>   * an error message and stores NULL in '*packetp'. */
> diff --git a/lib/packets.h b/lib/packets.h
> index 8e4117e..7f15629 100644
> --- a/lib/packets.h
> +++ b/lib/packets.h
> @@ -147,6 +147,15 @@ void eth_addr_bitand(const uint8_t src[ETH_ADDR_LEN],
>                       const uint8_t mask[ETH_ADDR_LEN],
>                       uint8_t dst[ETH_ADDR_LEN]);
>  
> +void set_mpls_ttl(struct ofpbuf *, uint8_t ttl);
> +void dec_mpls_ttl(struct ofpbuf *);
> +void copy_mpls_ttl_in(struct ofpbuf *);
> +void copy_mpls_ttl_out(struct ofpbuf *);
> +void set_mpls_tc(struct ofpbuf *, uint8_t tc);
> +void set_mpls_lse(struct ofpbuf *, ovs_be32 label);
> +void push_mpls (struct ofpbuf *packet, ovs_be16 ethtype);
> +void pop_mpls(struct ofpbuf *, ovs_be16 ethtype);
> +
>  /* Example:
>   *
>   * uint8_t mac[ETH_ADDR_LEN];
> @@ -181,6 +190,8 @@ void eth_addr_bitand(const uint8_t src[ETH_ADDR_LEN],
>  #define ETH_TYPE_IPV6          0x86dd
>  #define ETH_TYPE_LACP          0x8809
>  #define ETH_TYPE_RARP          0x8035
> +#define ETH_TYPE_MPLS          0x8847
> +#define ETH_TYPE_MPLS_MCAST    0x8848
>  
>  /* Minimum value for an Ethernet type.  Values below this are IEEE 802.2 frame
>   * lengths. */
> @@ -287,6 +298,76 @@ struct vlan_eth_header {
>  } __attribute__((packed));
>  BUILD_ASSERT_DECL(VLAN_ETH_HEADER_LEN == sizeof(struct vlan_eth_header));
>  
> +/* MPLS related definitions */
> +#define MPLS_TTL_MASK       0x000000ff
> +#define MPLS_TTL_SHIFT      0
> +
> +#define MPLS_STACK_MASK     0x00000100
> +#define MPLS_STACK_SHIFT    8
> +
> +#define MPLS_TC_MASK        0x00000e00
> +#define MPLS_TC_SHIFT       9
> +
> +#define MPLS_LABEL_MASK     0xfffff000
> +#define MPLS_LABEL_SHIFT    12
> +
> +#define MPLS_HLEN           4
> +
> +struct mpls_hdr {
> +    ovs_be32 mpls_lse;
> +};
> +BUILD_ASSERT_DECL(MPLS_HLEN == sizeof(struct mpls_hdr));
> +
> +#define MPLS_ETH_HEADER_LEN (ETH_HEADER_LEN + MPLS_HLEN)
> +struct mpls_eth_header {
> +    uint8_t  eth_dst[ETH_ADDR_LEN];
> +    uint8_t  eth_src[ETH_ADDR_LEN];
> +    ovs_be16 eth_type;         /* htons(ETH_TYPE_MPLS) or
> +                                  htons(ETH_TYPE_MPLS_MCAST). */
> +    ovs_be32 mpls_lse;
> +} __attribute__((packed));
> +BUILD_ASSERT_DECL(MPLS_ETH_HEADER_LEN == sizeof(struct mpls_eth_header));
> +
> +/* Given a mpls label stack entry in network byte order
> + * return mpls label */
> +static inline uint32_t
> +mpls_lse_to_label(ovs_be32 mpls_lse)
> +{
> +    return (ntohl(mpls_lse) & MPLS_LABEL_MASK) >> MPLS_LABEL_SHIFT;
> +}
> +
> +/* Given a mpls label stack entry in network byte order
> + * return mpls tc */
> +static inline int
> +mpls_lse_to_tc(ovs_be32 mpls_lse)
> +{
> +    return (ntohl(mpls_lse) & MPLS_TC_MASK) >> MPLS_TC_SHIFT;
> +}
> +
> +/* Given a mpls label stack entry in network byte order
> + * return mpls ttl */
> +static inline int
> +mpls_lse_to_ttl(ovs_be32 mpls_lse)
> +{
> +    return (ntohl(mpls_lse) & MPLS_TTL_MASK) >> MPLS_TTL_SHIFT;
> +}
> +
> +/* Set TTL in mpls lse. */
> +static inline void
> +flow_set_mpls_lse_ttl(ovs_be32 *mpls_lse, uint8_t ttl)
> +{
> +    *mpls_lse &= ~htonl(MPLS_TTL_MASK);
> +    *mpls_lse |= htonl(ttl << MPLS_TTL_SHIFT);
> +}
> +
> +/* Given a mpls label stack entry in network byte order
> + * return mpls stack */
> +static inline int
> +mpls_lse_to_stack(ovs_be32 mpls_lse)
> +{
> +    return (mpls_lse & htonl(MPLS_STACK_MASK)) != 0;
> +}
> +
>  /* The "(void) (ip)[0]" below has no effect on the value, since it's the first
>   * argument of a comma expression, but it makes sure that 'ip' is a pointer.
>   * This is useful since a common mistake is to pass an integer instead of a
> @@ -343,6 +424,8 @@ void ip_format_masked(ovs_be32 ip, ovs_be32 mask, struct ds *);
>  
>  #define IP_VERSION 4
>  
> +#define IP_DSCP(ip_tos) ((ip_tos & IP_DSCP_MASK) >> 2)
> +
>  #define IP_DONT_FRAGMENT  0x4000 /* Don't fragment. */
>  #define IP_MORE_FRAGMENTS 0x2000 /* More fragments. */
>  #define IP_FRAG_OFF_MASK  0x1fff /* Fragment offset. */
> @@ -442,6 +525,11 @@ BUILD_ASSERT_DECL(ARP_ETH_HEADER_LEN == sizeof(struct arp_eth_header));
>  /* The IPv6 flow label is in the lower 20 bits of the first 32-bit word. */
>  #define IPV6_LABEL_MASK 0x000fffff
>  
> +#define IP6_VERSION    6
> +
> +#define IP6_VER(ip6_vfc) ((ip6_vfc) >> 4)
> +#define IP6_TC(ip6_flow) ((ip6_flow >> 20) & 0xff)
> +
>  /* Example:
>   *
>   * char *string = "1 ::1 2";
> diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
> index 962df15..592d0b4 100644
> --- a/ofproto/ofproto-dpif.c
> +++ b/ofproto/ofproto-dpif.c
> @@ -4861,6 +4861,7 @@ compose_output_action__(struct action_xlate_ctx *ctx, uint16_t ofp_port,
>      const struct ofport_dpif *ofport = get_ofp_port(ctx->ofproto, ofp_port);
>      uint16_t odp_port = ofp_port_to_odp_port(ofp_port);
>      ovs_be16 flow_vlan_tci = ctx->flow.vlan_tci;
> +    ovs_be32 flow_mpls_lse = ctx->flow.mpls_lse;
>      uint8_t flow_nw_tos = ctx->flow.nw_tos;
>      uint16_t out_port;
>  
> @@ -4895,6 +4896,7 @@ compose_output_action__(struct action_xlate_ctx *ctx, uint16_t ofp_port,
>      ctx->sflow_n_outputs++;
>      ctx->nf_output_iface = ofp_port;
>      ctx->flow.vlan_tci = flow_vlan_tci;
> +    ctx->flow.mpls_lse = flow_mpls_lse;
>      ctx->flow.nw_tos = flow_nw_tos;
>  }
>  
> @@ -5037,6 +5039,11 @@ execute_controller_action(struct action_xlate_ctx *ctx, int len,
>              eth_push_vlan(packet, ctx->flow.vlan_tci);
>          }
>  
> +        if (ctx->flow.mpls_lse) {
> +            push_mpls(packet, ctx->flow.dl_type);
> +            set_mpls_lse(packet, ctx->flow.mpls_lse);
> +        }
> +
>          if (packet->l4) {
>              if (ctx->flow.dl_type == htons(ETH_TYPE_IP)) {
>                  packet_set_ipv4(packet, ctx->flow.nw_src, ctx->flow.nw_dst,
> @@ -5088,6 +5095,41 @@ compose_dec_ttl(struct action_xlate_ctx *ctx)
>      }
>  }
>  
> +static inline void
> +compose_dec_mpls_ttl(struct action_xlate_ctx *ctx)
> +{
> +    uint8_t ttl = mpls_lse_to_ttl(ctx->flow.mpls_lse);
> +    if (ttl > 1) {
> +        if (ctx->flow.mpls_lse != htonl(0)) {
> +            flow_set_mpls_lse_ttl(&ctx->flow.mpls_lse, --ttl);
> +        }
> +        if (ctx->base_flow.mpls_lse != htonl(0)) {
> +            flow_set_mpls_lse_ttl(&ctx->base_flow.mpls_lse, --ttl);
> +        }
> +        nl_msg_put_flag(ctx->odp_actions, OVS_ACTION_ATTR_DEC_MPLS_TTL);
> +    } else {
> +        execute_controller_action(ctx, UINT16_MAX, OFPR_INVALID_TTL, 0);
> +    }
> +}
> +
> +static inline void
> +compose_copy_mpls_ttl_in(struct action_xlate_ctx *ctx)
> +{
> +    nl_msg_put_flag(ctx->odp_actions, OVS_ACTION_ATTR_COPY_TTL_IN);
> +}
> +
> +static inline void
> +compose_copy_mpls_ttl_out(struct action_xlate_ctx *ctx)
> +{
> +    if (ctx->flow.mpls_lse != htonl(0)) {
> +        flow_set_mpls_lse_ttl(&ctx->flow.mpls_lse, ctx->flow.nw_ttl);
> +    }
> +    if (ctx->base_flow.mpls_lse != htonl(0)) {
> +        flow_set_mpls_lse_ttl(&ctx->base_flow.mpls_lse, ctx->flow.nw_ttl);
> +    }
> +    nl_msg_put_flag(ctx->odp_actions, OVS_ACTION_ATTR_COPY_TTL_OUT);
> +}
> +
>  static void
>  xlate_output_action__(struct action_xlate_ctx *ctx,
>                        uint16_t port, uint16_t max_len)
> @@ -5344,6 +5386,11 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
>      }
>      OFPUTIL_ACTION_FOR_EACH_UNSAFE (ia, left, in, n_in) {
>          const struct ofp_action_dl_addr *oada;
> +        const struct nx_action_mpls_label *naml;
> +        const struct nx_action_mpls_tc *namtc;
> +        const struct nx_action_mpls_ttl *namttl;
> +        const struct nx_action_push_mpls *nampush;
> +        const struct nx_action_pop_mpls *nampop;
>          const struct nx_action_resubmit *nar;
>          const struct nx_action_set_tunnel *nast;
>          const struct nx_action_set_queue *nasq;
> @@ -5354,6 +5401,7 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
>          const struct nx_action_controller *nac;
>          enum ofputil_action_code code;
>          ovs_be64 tun_id;
> +        ovs_be32 mpls_label;
>  
>          if (ctx->exit) {
>              break;
> @@ -5504,6 +5552,55 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
>              }
>              break;
>  
> +        case OFPUTIL_NXAST_PUSH_MPLS:
> +            nampush = (const struct nx_action_push_mpls *) ia;
> +            commit_mpls_push_action(&ctx->flow, &ctx->base_flow, ctx->odp_actions,
> +                                    nampush->ethertype);
> +            break;
> +
> +        case OFPUTIL_NXAST_POP_MPLS:
> +            nampop = (const struct nx_action_pop_mpls *) ia;
> +            ctx->flow.dl_type = nampop->ethertype;
> +            commit_mpls_pop_action(&ctx->flow, &ctx->base_flow, ctx->odp_actions);
> +            if (ctx->flow.mpls_lse != htonl(0)) {
> +                ctx->flow.mpls_lse = htonl(0);
> +            }
> +            break;
> +
> +        case OFPUTIL_NXAST_SET_MPLS_LABEL:
> +            naml = (const struct nx_action_mpls_label *) ia;
> +            mpls_label = htonl((ntohl(naml->mpls_label) << MPLS_LABEL_SHIFT));
> +            ctx->flow.mpls_lse &= ~htonl(MPLS_LABEL_MASK);
> +            ctx->flow.mpls_lse |= mpls_label;
> +            commit_mpls_lse_action(&ctx->flow, &ctx->base_flow, ctx->odp_actions);
> +            break;
> +
> +        case OFPUTIL_NXAST_SET_MPLS_TC:
> +            namtc = (const struct nx_action_mpls_tc *) ia;
> +            ctx->flow.mpls_lse &= ~htonl(MPLS_TC_MASK);
> +            ctx->flow.mpls_lse |= htonl((namtc->mpls_tc << MPLS_TC_SHIFT));
> +            commit_mpls_lse_action(&ctx->flow, &ctx->base_flow, ctx->odp_actions);
> +            break;
> +
> +        case OFPUTIL_NXAST_SET_MPLS_TTL:
> +            namttl = (const struct nx_action_mpls_ttl *) ia;
> +            ctx->flow.mpls_lse &= ~htonl(MPLS_TTL_MASK);
> +            ctx->flow.mpls_lse |= htonl((namttl->mpls_ttl << MPLS_TTL_SHIFT));
> +            commit_mpls_lse_action(&ctx->flow, &ctx->base_flow, ctx->odp_actions);
> +            break;
> +
> +        case OFPUTIL_NXAST_DEC_MPLS_TTL:
> +            compose_dec_mpls_ttl(ctx);
> +            break;
> +
> +        case OFPUTIL_NXAST_COPY_TTL_IN:
> +            compose_copy_mpls_ttl_in(ctx);
> +            break;
> +
> +        case OFPUTIL_NXAST_COPY_TTL_OUT:
> +            compose_copy_mpls_ttl_out(ctx);
> +            break;
> +
>          case OFPUTIL_NXAST_EXIT:
>              ctx->exit = true;
>              break;
> diff --git a/tests/automake.mk b/tests/automake.mk
> index b7e1b94..99e0b30 100644
> --- a/tests/automake.mk
> +++ b/tests/automake.mk
> @@ -100,6 +100,7 @@ lcov_wrappers = \
>  	tests/lcov/test-csum \
>  	tests/lcov/test-file_name \
>  	tests/lcov/test-flows \
> +	tests/lcov/test-mpls \
>  	tests/lcov/test-hash \
>  	tests/lcov/test-heap \
>  	tests/lcov/test-hmap \
> @@ -158,6 +159,7 @@ valgrind_wrappers = \
>  	tests/valgrind/test-csum \
>  	tests/valgrind/test-file_name \
>  	tests/valgrind/test-flows \
> +	tests/valgrind/test-mpls \
>  	tests/valgrind/test-hash \
>  	tests/valgrind/test-heap \
>  	tests/valgrind/test-hmap \
> @@ -244,6 +246,10 @@ tests_test_flows_SOURCES = tests/test-flows.c
>  tests_test_flows_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
>  dist_check_SCRIPTS = tests/flowgen.pl
>  
> +noinst_PROGRAMS += tests/test-mpls
> +tests_test_mpls_SOURCES = tests/test-mpls.c
> +tests_test_mpls_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
> +
>  noinst_PROGRAMS += tests/test-hash
>  tests_test_hash_SOURCES = tests/test-hash.c
>  tests_test_hash_LDADD = lib/libopenvswitch.a
> diff --git a/tests/odp.at b/tests/odp.at
> index 9617af2..f22bd69 100644
> --- a/tests/odp.at
> +++ b/tests/odp.at
> @@ -24,6 +24,11 @@ in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv
>  in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0,proto=58,tclass=0,hlimit=128,frag=no),icmpv6(type=136,code=0),nd(target=::3,tll=00:0a:0b:0c:0d:0e)
>  in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0,proto=58,tclass=0,hlimit=128,frag=no),icmpv6(type=136,code=0),nd(target=::3,sll=00:05:06:07:08:09,tll=00:0a:0b:0c:0d:0e)
>  in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0806),arp(sip=1.2.3.4,tip=5.6.7.8,op=1,sha=00:0f:10:11:12:13,tha=00:14:15:16:17:18)
> +in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8847),mpls(label=100,tc=3,ttl=64,bos=1)
> +in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8847),mpls(label=100,tc=7,ttl=100,bos=1)
> +in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8847),mpls(label=100,tc=7,ttl=100,bos=0)
> +in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8848),mpls(label=1000,tc=4,ttl=200,bos=1)
> +in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8848),mpls(label=1000,tc=4,ttl=200,bos=0)
>  ])
>  
>  (echo '# Valid forms without tun_id or VLAN header.'
> @@ -39,6 +44,14 @@ in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0806),arp
>  s/$/)/' odp-base.txt
>  
>   echo
> + echo '# Valid forms with MPLS header.'
> + sed 's/\(eth([[^)]]*)\),*/\1,eth_type(0x8847),mpls(label=100,tc=7,ttl=64,bos=1)' odp-base.txt
> +
> + echo
> + echo '# Valid forms with MPLS multicast header.'
> + sed 's/\(eth([[^)]]*)\),*/\1,eth_type(0x8848),mpls(label=100,tc=7,ttl=64,bos=1)' odp-base.txt
> +
> + echo
>   echo '# Valid forms with QoS priority.'
>   sed 's/^/priority(1234),/' odp-base.txt
>  
> @@ -89,6 +102,13 @@ push_vlan(vid=12,pcp=0)
>  push_vlan(vid=13,pcp=5,cfi=0)
>  push_vlan(tpid=0x9100,vid=13,pcp=5)
>  push_vlan(tpid=0x9100,vid=13,pcp=5,cfi=0)
> +push_mpls(eth_type=0x8847)
> +push_mpls(eth_type=0x8848)
> +mpls_lse(label=100,tc=3,ttl=64,bos=0)
> +mpls_lse(label=101,tc=4,ttl=100,bos=1)
> +dec_mpls_ttl
> +copy_ttl_in
> +copy_ttl_out
>  pop_vlan
>  sample(sample=9.7%,actions(1,2,3,push_vlan(vid=1,pcp=2)))
>  ])
> diff --git a/tests/ofp-print.at b/tests/ofp-print.at
> index 4b94fb4..7b07f89 100644
> --- a/tests/ofp-print.at
> +++ b/tests/ofp-print.at
> @@ -278,7 +278,7 @@ c0 a8 00 02 27 2f 00 00 78 50 cc 5b 57 af 42 1e \
>  50 00 02 00 26 e8 00 00 00 00 00 00 00 00 \
>  "], [0], [dnl
>  OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=3 (via no_match) data_len=60 buffer=0x00000111
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:06) type:0800 proto:6 tos:0 ttl:64 ip(192.168.0.1->192.168.0.2) port(10031->0) tcp_csum:26e8
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:06) type:0800,mpls(0) proto:6 tos:0 ttl:64 ip(192.168.0.1->192.168.0.2) port(10031->0) tcp_csum:26e8
>  ])
>  AT_CLEANUP
>  
> @@ -774,7 +774,7 @@ ff ff ff ff 00 40 01 07 00 00 00 00 00 00 00 09 \
>  31 6d 00 00 00 00 00 00 00 00 \
>  "], [0], [dnl
>  NXT_PACKET_IN (xid=0x0): table_id=7 cookie=0x9 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) tcp_csum:316d
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) tcp_csum:316d
>  ])
>  AT_CLEANUP
>  
> diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
> index 9c3e0dc..7936cf9 100644
> --- a/tests/ofproto-dpif.at
> +++ b/tests/ofproto-dpif.at
> @@ -97,7 +97,7 @@ AT_CHECK([ovs-appctl ofproto/trace br0 'in_port(1),eth(src=50:54:00:00:00:05,dst
>  OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
>  AT_CHECK([cat ofctl_monitor.log], [0], [dnl
>  NXT_PACKET_IN (xid=0x0): table_id=1 total_len=42 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via invalid_ttl) data_len=42 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800 proto:1 tos:0 ttl:1 ip(192.168.0.1->192.168.0.2)
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800,mpls(0) proto:1 tos:0 ttl:1 ip(192.168.0.1->192.168.0.2)
>  ])
>  OVS_VSWITCHD_STOP
>  AT_CLEANUP
> @@ -249,6 +249,10 @@ cookie=0x6 table=4 in_port=83 actions=load:4->NXM_NX_REG3[[]],mod_nw_src:83.83.8
>  cookie=0x7 table=5 in_port=84 actions=load:5->NXM_NX_REG4[[]],load:6->NXM_NX_TUN_ID[[]],mod_nw_dst:84.84.84.84,controller,resubmit(85,6)
>  cookie=0x8 table=6 in_port=85 actions=mod_tp_src:85,controller,resubmit(86,7)
>  cookie=0x9 table=7 in_port=86 actions=mod_tp_dst:86,controller,controller
> +cookie=0xa dl_src=40:44:44:44:44:44 actions=push_mpls:0x8847,set_mpls_label:10,set_mpls_tc:3,set_mpls_ttl:64,dec_mpls_ttl,controller
> +cookie=0xb dl_src=50:55:55:55:55:55 actions=set_mpls_label:1000,set_mpls_ttl:200,copy_ttl_out,controller
> +cookie=0xd dl_src=60:66:66:66:66:66 actions=pop_mpls:0x0800,controller
> +cookie=0xc dl_src=70:77:77:77:77:77 actions=push_mpls:0x8848,set_mpls_label:1000,set_mpls_tc:7,set_mpls_ttl:250,controller
>  ])
>  AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
>  
> @@ -262,13 +266,13 @@ done
>  OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
>  AT_CHECK([cat ofctl_monitor.log], [0], [dnl
>  OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=1 (via no_match) data_len=60 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->9) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->9) tcp_csum:0
>  dnl
>  OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=1 (via no_match) data_len=60 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->9) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->9) tcp_csum:0
>  dnl
>  OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=1 (via no_match) data_len=60 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->9) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->9) tcp_csum:0
>  ])
>  
>  dnl Singleton controller action.
> @@ -281,13 +285,13 @@ done
>  OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
>  AT_CHECK([cat ofctl_monitor.log], [0], [dnl
>  OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=1 (via action) data_len=60 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(10:11:11:11:11:11->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(10:11:11:11:11:11->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
>  dnl
>  OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=1 (via action) data_len=60 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(10:11:11:11:11:11->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(10:11:11:11:11:11->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
>  dnl
>  OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=1 (via action) data_len=60 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(10:11:11:11:11:11->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(10:11:11:11:11:11->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
>  ])
>  
>  dnl Modified controller action.
> @@ -300,13 +304,89 @@ done
>  OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
>  AT_CHECK([cat ofctl_monitor.log], [0], [dnl
>  OFPT_PACKET_IN (xid=0x0): total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:15,pcp:0) mac(30:33:33:33:33:33->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(vlan:15,pcp:0) mac(30:33:33:33:33:33->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
>  dnl
>  OFPT_PACKET_IN (xid=0x0): total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:15,pcp:0) mac(30:33:33:33:33:33->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(vlan:15,pcp:0) mac(30:33:33:33:33:33->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
>  dnl
>  OFPT_PACKET_IN (xid=0x0): total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:15,pcp:0) mac(30:33:33:33:33:33->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(vlan:15,pcp:0) mac(30:33:33:33:33:33->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
> +])
> +
> +dnl Modified mpls controller action.
> +AT_CHECK([ovs-ofctl monitor br0 65534 -P nxm --detach --pidfile 2> ofctl_monitor.log])
> +
> +for i in 1 2 3; do
> +    ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=40:44:44:44:44:44,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no)'
> +done
> +
> +OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
> +AT_CHECK([cat ofctl_monitor.log], [0], [dnl
> +NXT_PACKET_IN (xid=0x0): cookie=0xa total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=48 (unbuffered)
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(40:44:44:44:44:44->50:54:00:00:00:07) type:8847,mpls(label:10,tc:3,ttl:64,bos:1) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
> +dnl
> +NXT_PACKET_IN (xid=0x0): cookie=0xa total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=48 (unbuffered)
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(40:44:44:44:44:44->50:54:00:00:00:07) type:8847,mpls(label:10,tc:3,ttl:64,bos:1) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
> +dnl
> +NXT_PACKET_IN (xid=0x0): cookie=0xa total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(40:44:44:44:44:44->50:54:00:00:00:07) type:8847,mpls(label:10,tc:3,ttl:64,bos:1) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
> +])
> +
> +dnl Modified mpls ttl action.
> +AT_CHECK([ovs-ofctl monitor br0 65534 -P nxm --detach --pidfile 2> ofctl_monitor.log])
> +
> +for i in 1 2 3; do
> +    ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:55:55:55:55:55,dst=50:54:00:00:00:07),eth_type(0x8847),mpls(label=100,tc=7,ttl=64,bos=1),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no)'
> +done
> +
> +OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
> +AT_CHECK([cat ofctl_monitor.log], [0], [dnl
> +NXT_PACKET_IN (xid=0x0): cookie=0xb total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(50:55:55:55:55:55->50:54:00:00:00:07) type:8847,mpls(label:1000,tc:7,ttl:200,bos:1),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no)
> +dnl
> +NXT_PACKET_IN (xid=0x0): cookie=0xb total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(50:55:55:55:55:55->50:54:00:00:00:07) type:8847,mpls(label:1000,tc:7,ttl:200,bos:1),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no)
> +dnl
> +NXT_PACKET_IN (xid=0x0): cookie=0xb total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(50:55:55:55:55:55->50:54:00:00:00:07) type:8847,mpls(label:1000,tc:7,ttl:200,bos:1),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no)
> +])
> +
> +dnl Modified mpls ipv6 controller action.
> +AT_CHECK([ovs-ofctl monitor br0 65534 -P nxm --detach --pidfile 2> ofctl_monitor.log])
> +
> +for i in 1 2 3; do
> +    ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=70:77:77:77:77:77,dst=50:54:00:00:00:07),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0,proto=10,tclass=0x70,hlimit=128,frag=no)'
> +done
> +
> +OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
> +AT_CHECK([cat ofctl_monitor.log], [0], [dnl
> +NXT_PACKET_IN (xid=0x0): cookie=0xc total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(70:77:77:77:77:77->50:54:00:00:00:07) type:8848,mpls(label:1000,tc:7,ttl:250,bos:1) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
> +dnl
> +NXT_PACKET_IN (xid=0x0): cookie=0xc total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(70:77:77:77:77:77->50:54:00:00:00:07) type:8848,mpls(label:1000,tc:7,ttl:250,bos:1) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
> +dnl
> +NXT_PACKET_IN (xid=0x0): cookie=0xc total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(70:77:77:77:77:77->50:54:00:00:00:07) type:8848,mpls(label:1000,tc:7,ttl:250,bos:1) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
> +])
> +
> +dnl Modified mpls pop action.
> +AT_CHECK([ovs-ofctl monitor br0 65534 -P nxm --detach --pidfile 2> ofctl_monitor.log])
> +
> +for i in 1 2 3; do
> +    ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=60:66:66:66:66:66,dst=50:54:00:00:00:07),eth_type(0x8847),mpls(label=10,tc=3,ttl=100,bos=1),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no)'
> +done
> +
> +OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
> +AT_CHECK([cat ofctl_monitor.log], [0], [dnl
> +NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=60 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> +priority:0,tunnel:0,in_port:0000,tci(0), mac(60:66:66:66:66:66->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2)
> +dnl
> +NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=60 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> +priority:0,tunnel:0,in_port:0000,tci(0), mac(60:66:66:66:66:66->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2)
> +dnl
> +NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=60 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> +priority:0,tunnel:0,in_port:0000,tci(0), mac(60:66:66:66:66:66->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2)
>  ])
>  
>  dnl Checksum TCP.
> @@ -319,31 +399,31 @@ done
>  OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
>  AT_CHECK([cat ofctl_monitor.log], [0], [dnl
>  NXT_PACKET_IN (xid=0x0): cookie=0x1 total_len=60 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=60 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x3 total_len=64 in_port=1 tun_id=0x0 reg0=0x1 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=2 cookie=0x4 total_len=64 in_port=1 tun_id=0x0 reg0=0x1 reg1=0x2 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->50:54:00:00:00:07) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=3 cookie=0x5 total_len=64 in_port=1 tun_id=0x0 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=4 cookie=0x6 total_len=64 in_port=1 tun_id=0x0 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:6 tos:0 ttl:0 ip(83.83.83.83->192.168.0.2) port(8->11) tcp_csum:1a03
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->192.168.0.2) port(8->11) tcp_csum:1a03
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=5 cookie=0x7 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(8->11) tcp_csum:3205
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(8->11) tcp_csum:3205
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=6 cookie=0x8 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->11) tcp_csum:31b8
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->11) tcp_csum:31b8
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=7 cookie=0x9 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) tcp_csum:316d
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) tcp_csum:316d
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=7 cookie=0x9 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) tcp_csum:316d
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) tcp_csum:316d
>  ])
>  
>  dnl Checksum UDP.
> @@ -356,31 +436,31 @@ done
>  OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
>  AT_CHECK([cat ofctl_monitor.log], [0], [dnl
>  NXT_PACKET_IN (xid=0x0): cookie=0x1 total_len=60 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=60 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800 proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x3 total_len=64 in_port=1 tun_id=0x0 reg0=0x1 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800 proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=2 cookie=0x4 total_len=64 in_port=1 tun_id=0x0 reg0=0x1 reg1=0x2 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->50:54:00:00:00:07) type:0800 proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->50:54:00:00:00:07) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=3 cookie=0x5 total_len=64 in_port=1 tun_id=0x0 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=4 cookie=0x6 total_len=64 in_port=1 tun_id=0x0 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:17 tos:0 ttl:0 ip(83.83.83.83->192.168.0.2) port(8->11) udp_csum:2c37
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(83.83.83.83->192.168.0.2) port(8->11) udp_csum:2c37
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=5 cookie=0x7 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(8->11) udp_csum:4439
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(8->11) udp_csum:4439
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=6 cookie=0x8 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->11) udp_csum:43ec
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->11) udp_csum:43ec
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=7 cookie=0x9 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) udp_csum:43a1
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) udp_csum:43a1
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=7 cookie=0x9 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800 proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) udp_csum:43a1
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) udp_csum:43a1
>  ])
>  
>  AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
> @@ -393,6 +473,10 @@ AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
>   cookie=0x7, table=5, n_packets=2, n_bytes=120, in_port=84 actions=load:0x5->NXM_NX_REG4[[]],load:0x6->NXM_NX_TUN_ID[[]],mod_nw_dst:84.84.84.84,CONTROLLER:65535,resubmit(85,6)
>   cookie=0x8, table=6, n_packets=2, n_bytes=120, in_port=85 actions=mod_tp_src:85,CONTROLLER:65535,resubmit(86,7)
>   cookie=0x9, table=7, n_packets=2, n_bytes=120, in_port=86 actions=mod_tp_dst:86,CONTROLLER:65535,CONTROLLER:65535
> + cookie=0xa, n_packets=3, n_bytes=180, dl_src=40:44:44:44:44:44 actions=push_mpls:0x8847,set_mpls_label:10,set_mpls_tc:3,set_mpls_ttl:64,dec_mpls_ttl,CONTROLLER:65535
> + cookie=0xb, n_packets=3, n_bytes=180, dl_src=50:55:55:55:55:55 actions=set_mpls_label:1000,set_mpls_ttl:200,copy_ttl_out,CONTROLLER:65535
> + cookie=0xd, n_packets=3, n_bytes=180, dl_src=60:66:66:66:66:66 actions=pop_mpls:0x0800,CONTROLLER:65535
> + cookie=0xc, n_packets=3, n_bytes=180, dl_src=70:77:77:77:77:77 actions=push_mpls:0x8848,set_mpls_label:1000,set_mpls_tc:7,set_mpls_ttl:250,dec_mpls_ttl,CONTROLLER:65535
>   n_packets=3, n_bytes=180, dl_src=10:11:11:11:11:11 actions=CONTROLLER:65535
>  NXST_FLOW reply:
>  ])
> diff --git a/tests/ofproto.at b/tests/ofproto.at
> index dbe31c4..d1ea8a0 100644
> --- a/tests/ofproto.at
> +++ b/tests/ofproto.at
> @@ -580,21 +580,21 @@ check_async () {
>      ovs-ofctl -v packet-out br0 none controller '0001020304050010203040501234'
>      if test X"$1" = X"OFPR_ACTION"; then shift;
>          echo >>expout "OFPT_PACKET_IN: total_len=14 in_port=NONE (via action) data_len=14 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:1234 proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)"
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:1234,mpls(0) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)"
>      fi
>  
>      # OFPT_PACKET_IN, OFPR_NO_MATCH (controller_id=123)
>      ovs-ofctl -v packet-out br0 none 'controller(reason=no_match,id=123)' '0001020304050010203040501234'
>      if test X"$1" = X"OFPR_NO_MATCH"; then shift;
>          echo >>expout "OFPT_PACKET_IN: total_len=14 in_port=NONE (via no_match) data_len=14 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:1234 proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)"
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:1234,mpls(0) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)"
>      fi
>  
>      # OFPT_PACKET_IN, OFPR_INVALID_TTL (controller_id=0)
>      ovs-ofctl packet-out br0 none dec_ttl '002583dfb4000026b98cb0f908004500003fb7e200000011339bac11370dac100002d7730035002b8f6d86fb0100000100000000000006626c702d7873066e696369726103636f6d00000f00'
>      if test X"$1" = X"OFPR_INVALID_TTL"; then shift;
>          echo >>expout "OFPT_PACKET_IN: total_len=76 in_port=NONE (via invalid_ttl) data_len=76 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(00:26:b9:8c:b0:f9->00:25:83:df:b4:00) type:0800 proto:17 tos:0 ttl:0 ip(172.17.55.13->172.16.0.2) port(55155->53) udp_csum:8f6d"
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(00:26:b9:8c:b0:f9->00:25:83:df:b4:00) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(172.17.55.13->172.16.0.2) port(55155->53) udp_csum:8f6d"
>      fi
>  
>      # OFPT_PORT_STATUS, OFPPR_ADD
> @@ -692,9 +692,9 @@ ovs-appctl -t ovs-ofctl exit
>  
>  AT_CHECK([sed 's/ (xid=0x[[0-9a-fA-F]]*)//' monitor.log], [0], [dnl
>  OFPT_PACKET_IN: total_len=14 in_port=NONE (via action) data_len=14 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:1234 proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:1234,mpls(0) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
>  OFPT_PACKET_IN: total_len=14 in_port=CONTROLLER (via action) data_len=14 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:5678 proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
> +priority:0,tunnel:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:5678,mpls(0) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
>  OFPT_BARRIER_REPLY:
>  ])
>  
> diff --git a/tests/test-mpls.c b/tests/test-mpls.c
> new file mode 100644
> index 0000000..169b26e
> --- /dev/null
> +++ b/tests/test-mpls.c
> @@ -0,0 +1,288 @@
> +/*
> + * Copyright (c) 2012 Nicira, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <sys/socket.h>
> +#include <features.h>
> +#ifndef __aligned_u64
> +#define __aligned_u64 __u64 __attribute__((aligned(8)))
> +#endif
> +#include <linux/if_packet.h>
> +#include <linux/if_ether.h>
> +#include <arpa/inet.h>
> +#include <errno.h>
> +#include <sys/ioctl.h>
> +#include <net/if.h>
> +#include "csum.h"
> +#include "packets.h"
> +
> +#define PKT_LENGTH	     512
> +#define ETH_DST_ADDR_OFF     0
> +#define ETH_SRC_ADDR_OFF     ETH_DST_ADDR_OFF  + ETH_ALEN
> +#define ETH_TYPE_ADDR_OFF    ETH_SRC_ADDR_OFF  + ETH_ALEN
> +#define VLAN_TPID_ADDR_OFF   ETH_TYPE_ADDR_OFF
> +#define VLAN_VID_ADDR_OFF    VLAN_TPID_ADDR_OFF + 2
> +#define VLAN_TYPE_ADDR_OFF   VLAN_VID_ADDR_OFF + 2
> +#define MPLS_HDR_ADDR_OFF    ETH_TYPE_ADDR_OFF + 2
> +#define IP_HDR_ADDR_OFF      MPLS_HDR_ADDR_OFF + 4
> +
> +struct vlan_hdr {
> +    uint16_t value;
> +};
> +
> +static int
> +create_sock (int proto)
> +{
> +    int sock_fd;
> +
> +    if ((sock_fd = socket(AF_PACKET, SOCK_RAW, proto)) == -1) {
> +        perror("Error creating socket: ");
> +        exit(-1);
> +    }
> +    return sock_fd;
> +}
> +
> +static int
> +bind_sock (char *device, int sock_fd, int protocol)
> +{
> +
> +    struct sockaddr_ll sll;
> +    struct ifreq ifr;
> +    bzero(&sll, sizeof(sll));
> +    bzero(&ifr, sizeof(ifr));
> +
> +    /* First Get the Interface Index  */
> +    strncpy((char *)ifr.ifr_name, device, IFNAMSIZ);
> +    if ((ioctl(sock_fd, SIOCGIFINDEX, &ifr)) == -1) {
> +        printf("Error getting Interface index !\n");
> +        exit(-1);
> +    }
> +
> +    /* Bind socket to this interface */
> +    sll.sll_family = AF_PACKET;
> +    sll.sll_ifindex = ifr.ifr_ifindex;
> +    sll.sll_protocol = htons(protocol);
> +
> +    if ((bind(sock_fd, (struct sockaddr *)&sll, sizeof(sll)))== -1) {
> +        perror("Error binding socket to interface\n");
> +        exit(-1);
> +    }
> +
> +    return 1;
> +}
> +
> +static int
> +send_pkt (int sock_fd, uint8_t *pkt, int pkt_len)
> +{
> +    int sent = 0;
> +
> +    /* A simple write on the socket ..thats all it takes ! */
> +
> +    if ((sent = write(sock_fd, pkt, pkt_len)) != pkt_len) {
> +        return 0;
> +    }
> +    return 1;
> +}
> +
> +static void
> +write_ether_type (uint8_t *pkt, uint16_t eth_type)
> +{
> +    ovs_be16 tmp_eth_type;
> +    tmp_eth_type = htons(eth_type);
> +    memcpy((void*)pkt, (void*)&tmp_eth_type, 2);
> +}
> +
> +static void
> +write_ether_hdr (uint8_t *pkt, uint16_t eth_type)
> +{
> +    ovs_be16 tmp_eth_type;
> +    /*MAC address of the host*/
> +    uint8_t src_mac[ETH_ALEN] = {0x00, 0x27, 0x13, 0x67, 0xb9, 0x9b};
> +
> +    /*gateway MAC address*/
> +    uint8_t dest_mac[ETH_ALEN] = {0x00, 0x1f, 0x9e, 0x2a, 0x7f, 0xdd};
> +
> +    tmp_eth_type = htons(eth_type);
> +
> +    memcpy((void*)(pkt + ETH_DST_ADDR_OFF), (void*)dest_mac, ETH_ALEN);
> +    memcpy((void*)(pkt + ETH_SRC_ADDR_OFF), (void*)src_mac, ETH_ALEN);
> +    memcpy((void*)(pkt + ETH_TYPE_ADDR_OFF), (void*)&tmp_eth_type, 2);
> +}
> +
> +static void
> +write_vlan_hdr (uint8_t *pkt, uint16_t vid, uint16_t pcp, uint16_t id)
> +{
> +    struct vlan_hdr vlan_h;
> +    ovs_be16 vlan_raw;
> +    ovs_be16 tpid = htons(id);
> +
> +    vlan_h.value = ((vid << VLAN_VID_SHIFT) & VLAN_VID_MASK) |
> +                   ((pcp << VLAN_PCP_SHIFT) & VLAN_PCP_MASK);
> +
> +    vlan_raw = htons(vlan_h.value);
> +
> +    memcpy((void*)pkt, (void *)&tpid, 2);
> +    memcpy((void*)(pkt+2), (void *) &vlan_raw, 2);
> +}
> +
> +static void
> +write_mpls_hdr (uint8_t *pkt, uint32_t label,
> +                uint32_t tc, uint32_t s, uint32_t ttl)
> +{
> +    struct mpls_hdr mpls_h;
> +
> +    mpls_h.mpls_lse = htonl(((ttl << MPLS_TTL_SHIFT) &  MPLS_TTL_MASK)  |
> +                            ((tc << MPLS_TC_SHIFT) & MPLS_TC_MASK)      |
> +                            ((s << MPLS_STACK_SHIFT) & MPLS_STACK_MASK) |
> +                            ((label << MPLS_LABEL_SHIFT) & MPLS_LABEL_MASK));
> +
> +    memcpy((void*)(pkt), (void *) &mpls_h.mpls_lse, 4);
> +}
> +
> +static void
> +write_ip_hdr (uint8_t *pkt, uint16_t ip_pkt_len)
> +{
> +     uint8_t ip_hdr[20] = { 0x45, 0x07, 0x00, 0x00,
> +                            0x00, 0x00, 0x00, 0x00,
> +                            0x10, 0x11, 0xa3, 0xfc,
> +                            0x0a, 0x75, 0x2e, 0xc8,
> +                            0x0a, 0x75, 0x2e, 0xc1};
> +
> +    ip_hdr[2] = (0xFF00 & ip_pkt_len) >> 8;
> +    ip_hdr[3] = 0x00FF & ip_pkt_len;
> +
> +    memcpy((void *)(pkt), (void *) &ip_hdr, 20);
> +}
> +
> +static void
> +write_udp_hdr (uint8_t *pkt, uint16_t udp_len)
> +{
> +    uint8_t udp_hdr[8] = {0x0F, 0x00, 0x0F, 0x00,
> +                          0x00, 0x00, 0x00, 0x00};
> +    udp_hdr[4] = (0xFF00 & udp_len) >> 8;
> +    udp_hdr[5] = (0x00FF & udp_len);
> +    memcpy((void *)(pkt), (void *) &udp_hdr, 8);
> +}
> +
> +static void
> +write_ip_csum (uint8_t *pkt, uint16_t len)
> +{
> +    /* len should be just the length of the header */
> +    ovs_be16 ip_csum = 0;
> +
> +    /* initialize the ip checksum field to 0 for
> +     * purposes of calculating the header */
> +    memcpy(pkt + 10, &ip_csum, 2);
> +
> +    /* appears to return in network byte order somehow */
> +    ip_csum = csum(pkt, len);
> +    memcpy(pkt + 10, &ip_csum, 2);
> +}
> +
> +/* argv[1] is the device e.g. eth0
> +   argv[2] is the number of pkts to send
> +*/
> +int
> +main (int argc, char **argv)
> +{
> +
> +    int sock_fd;
> +    uint8_t pkt[PKT_LENGTH];
> +    uint8_t *pkt_pos = pkt;
> +    uint8_t *ip_pos;
> +    uint32_t label = 100, tc = 4, ttl = 10;
> +    uint16_t vid = 101, pcp = 4;
> +    uint32_t num_of_pkts, num_labels;
> +    uint16_t i = 0;
> +    char *str = "FEEDFACE", type[5];
> +
> +    if (argc != 5) {
> +        printf("usage: %s <device> <# pkts> <#labels> <type=vlan/mpls>\n", argv[0]);
> +        return -1;
> +    }
> +
> +    num_of_pkts = atoi(argv[2]);
> +
> +    strncpy(type, argv[argc-1], 5);
> +
> +    /* Set the magic data 0xfeedface */
> +    for (i = 0; i < PKT_LENGTH; i+=8) {
> +        memcpy((void*)(pkt + i), (void*)str, 8);
> +    }
> +
> +    num_labels = atoi(argv[3]);
> +
> +    if (!strcmp(type, "vlan")) {
> +        write_ether_hdr(pkt_pos, ETH_TYPE_IP);
> +        pkt_pos += ETH_TYPE_ADDR_OFF;
> +        for (i = 0; i < num_labels; i++) {
> +            if (i == 1 || num_labels == 1) {
> +                write_vlan_hdr(pkt_pos, vid++, pcp++, ETH_TYPE_VLAN);
> +            }
> +            else {
> +                write_vlan_hdr(pkt_pos, vid++, pcp++, ETH_TYPE_VLAN);
> +            }
> +            pkt_pos += 4;
> +        }
> +        write_ether_type(pkt_pos, ETH_TYPE_IP);
> +        pkt_pos+=2;
> +    } else {
> +        write_ether_hdr(pkt_pos, ETH_TYPE_MPLS);
> +        pkt_pos += MPLS_HDR_ADDR_OFF;
> +        for (i = 0; i < num_labels; i++) {
> +            if (i == num_labels - 1) {
> +                write_mpls_hdr(pkt_pos, label++, tc, 1, ttl++);
> +            } else {
> +                write_mpls_hdr(pkt_pos, label++, tc, 0, ttl++);
> +            }
> +            pkt_pos += 4;
> +        }
> +    }
> +
> +    ip_pos = pkt_pos;
> +    write_ip_hdr(pkt_pos, PKT_LENGTH - (ip_pos - pkt));
> +    pkt_pos += 20;
> +
> +    write_udp_hdr(pkt_pos, PKT_LENGTH -(pkt_pos - pkt));
> +    pkt_pos += 8;
> +
> +    write_ip_csum(ip_pos, 20);
> +
> +    /* Create the socket */
> +    sock_fd = create_sock(ETH_P_ALL);
> +
> +    /* Bind socket to interface */
> +    bind_sock(argv[1], sock_fd, ETH_P_ALL);
> +
> +    while ((num_of_pkts--) > 0) {
> +        if (!send_pkt(sock_fd, pkt, PKT_LENGTH)) {
> +            perror("Error sending pkt");
> +            printf("\n\n");
> +            break;
> +        }
> +    }
> +    printf("\nPrinting packet\n");
> +    for (i = 0; i < 50; i++)
> +        printf("%x ", pkt[i]);
> +    if (num_of_pkts == -1)
> +        printf("Packets sent successfully\n");
> +
> +    close(sock_fd);
> +    return 0;
> +}
> diff --git a/utilities/ovs-dpctl.c b/utilities/ovs-dpctl.c
> index 6cb05b8..1322994 100644
> --- a/utilities/ovs-dpctl.c
> +++ b/utilities/ovs-dpctl.c
> @@ -882,17 +882,28 @@ do_normalize_actions(int argc, char *argv[])
>  
>      hmap_init(&actions_per_flow);
>      NL_ATTR_FOR_EACH (a, left, odp_actions.data, odp_actions.size) {
> -        if (nl_attr_type(a) == OVS_ACTION_ATTR_POP_VLAN) {
> +        const struct ovs_action_push_vlan *push;
> +        switch(nl_attr_type(a)) {
> +        case OVS_ACTION_ATTR_POP_VLAN:
>              flow.vlan_tci = htons(0);
>              continue;
> -        }
> -
> -        if (nl_attr_type(a) == OVS_ACTION_ATTR_PUSH_VLAN) {
> -            const struct ovs_action_push_vlan *push;
>  
> +        case OVS_ACTION_ATTR_PUSH_VLAN:
>              push = nl_attr_get_unspec(a, sizeof *push);
>              flow.vlan_tci = push->vlan_tci;
>              continue;
> +
> +        case OVS_ACTION_ATTR_PUSH_MPLS:
> +            flow.dl_type = nl_attr_get_be16(a);
> +            continue;
> +
> +        case OVS_ACTION_ATTR_POP_MPLS:
> +            flow.dl_type = nl_attr_get_be16(a);
> +            continue;
> +
> +        case OVS_ACTION_ATTR_SET_MPLS_LSE:
> +            flow.mpls_lse = nl_attr_get_be32(a);
> +            continue;
>          }
>  
>          af = get_actions_for_flow(&actions_per_flow, &flow);
> @@ -923,6 +934,15 @@ do_normalize_actions(int argc, char *argv[])
>              printf("no vlan: ");
>          }
>  
> +        if (af->flow.mpls_lse != htonl(0)) {
> +            printf("mpls(label=%"PRIu32",tc=%d,ttl=%d): ",
> +                   mpls_lse_to_label(af->flow.mpls_lse),
> +                   mpls_lse_to_tc(af->flow.mpls_lse),
> +                   mpls_lse_to_ttl(af->flow.mpls_lse));
> +        } else {
> +            printf("no mpls: ");
> +        }
> +
>          ds_clear(&s);
>          format_odp_actions(&s, af->actions.data, af->actions.size);
>          puts(ds_cstr(&s));
> diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
> index 085a2c2..b3653e1 100644
> --- a/utilities/ovs-ofctl.8.in
> +++ b/utilities/ovs-ofctl.8.in
> @@ -409,6 +409,21 @@ integer between 0 and 65535, inclusive, either in decimal or as a
>  hexadecimal number prefixed by \fB0x\fR (e.g. \fB0x0806\fR to match ARP 
>  packets).
>  .
> +.IP \fBmpls_label=\fIlabel\fR
> +Matches MPLS Label when \fIethertype\fR is \fI0x8847\fR or \fI0x8848\fR.
> +Specify a number between 0 and 2^20-1, inclusive, as the 20-bit MPLS label
> +to match. If none specified, all packets which has \fIethertype\fR equal to
> +\fI0x8847\fR or \fI0x8848\fR are matched.
> +.
> +.IP \fBmpls_tc=\fIpriority\fR
> +Matches MPLS traffic class when \fIethertype\fR is \fI0x8847\fR or
> +\fI0x8848\fR.  Specify a value between 0 and 7, inclusive.  A higher value
> +indicates a higher packet priority level.
> +.
> +.IP \fBmpls_stack=\fIstack\fR
> +Matches MPLS stack bit when \fIethertype\fR is \fI0x8847\fR or \fI0x8848\fR.
> +Specify either 0 or 1 to match a packet with a single label or multiple labels.
> +.
>  .IP \fBnw_src=\fIip\fR[\fB/\fInetmask\fR]
>  .IQ \fBnw_dst=\fIip\fR[\fB/\fInetmask\fR]
>  When \fBdl_type\fR is 0x0800 (possibly via shorthand, e.g. \fBip\fR
> @@ -838,6 +853,36 @@ as necessary to match the value specified.  Valid values are between 0
>  .IP \fBstrip_vlan\fR
>  Strips the VLAN tag from a packet if it is present.
>  .
> +.IP \fBpush_mpls\fR:\fIethertype\fR
> +Modifies the ethertype of a packet with the value specified. The new MPLS
> +label stack entry is set with value copied from outermost MPLS label stack
> +entry if present and stack bit set to 0 or MPLS label stack entry is set with
> +\fBlabel\fR:\fI0 for IPv4, 2 for IPv6 or default 0\fR, \fBtc\fR:\fIleast
> +significant 3 bits from IPv4 DSCP or IPv6 traffic-class or 0\fR,
> +\fBstack\fR:\fI1\fR, \fBttl\fR:\fIIPv4 ttl or IPv6 hlim or default 64\fR.
> +.
> +.IP \fBpop_mpls\fR:\fIethertype\fR
> +Strips the outermost MPLS label stack entry and modifies the ethertype of a
> +packet with the value specified if MPLS stack bit is set(i.e. bottom of stack).
> +.
> +.IP \fBset_mpls_label\fR:\fIlabel\fR
> +Modifies the outermost MPLS label stack entry's label field.
> +.
> +.IP \fBset_mpls_ttl\fR:\fIttl\fR
> +Modifies the outermost MPLS label stack entry's time-to-live field.
> +.
> +.IP \fBset_mpls_tc\fR:\fItc\fR
> +Modifies the outermost MPLS label stack entry's traffic-class field.
> +.
> +.IP \fBcopy_mpls_ttl_in\fR
> +Copies ttl from outermost MPLS header to next-to-outermost MPLS or IP header.
> +.
> +.IP \fBcopy_mpls_ttl_out\fR
> +Copies ttl from next-to-outermost IP or MPLS header to outermost MPLS header.
> +.
> +.IP \fBdec_mpls_ttl\fR
> +Decrements ttl from outermost MPLS header.
> +.
>  .IP \fBmod_dl_src\fB:\fImac\fR
>  Sets the source Ethernet address to \fImac\fR.
>  .
> -- 
> 1.7.5.4
> 

> From bf0bca1b66068102e50bf1e9ddc50093d28c2024 Mon Sep 17 00:00:00 2001
> From: Ravi K <rkerur at gmail.com>
> Date: Mon, 11 Jun 2012 14:50:29 -0700
> Subject: [PATCH 2/2] OF 1.1 VLAN QinQ changes.
> 
> Signed-off-by: Ravi K <rkerur at gmail.com>
> ---
>  datapath/actions.c                                 |   32 +++-
>  datapath/datapath.c                                |    8 +-
>  datapath/datapath.h                                |    2 +
>  datapath/flow.c                                    |  108 ++++++++++--
>  datapath/flow.h                                    |    4 +
>  datapath/linux/compat/include/linux/if_vlan.h      |   37 ++++
>  .../linux/compat/include/linux/netfilter_bridge.h  |    1 +
>  datapath/linux/compat/netdevice.c                  |    9 +-
>  datapath/tunnel.c                                  |  122 +++++++++----
>  datapath/vlan.c                                    |   42 +++++
>  datapath/vlan.h                                    |   33 ++++-
>  datapath/vport-gre.c                               |    3 +-
>  datapath/vport-internal_dev.c                      |    2 +
>  datapath/vport-netdev.c                            |   98 ++++++++---
>  datapath/vport.h                                   |    6 +-
>  include/linux/openvswitch.h                        |   11 +-
>  include/openflow/nicira-ext.h                      |   48 +++++
>  lib/bond.c                                         |    2 +-
>  lib/cfm.c                                          |    2 +-
>  lib/classifier.c                                   |   47 +++++-
>  lib/classifier.h                                   |    3 +
>  lib/dpif-netdev.c                                  |    3 +-
>  lib/flow.c                                         |  121 ++++++++++++--
>  lib/flow.h                                         |   24 ++-
>  lib/learn.c                                        |    2 +-
>  lib/meta-flow.c                                    |  116 +++++++++++++
>  lib/meta-flow.h                                    |    8 +
>  lib/nx-match.c                                     |   16 ++-
>  lib/nx-match.h                                     |    2 +-
>  lib/odp-util.c                                     |  120 ++++++++++++-
>  lib/odp-util.h                                     |    3 +-
>  lib/ofp-parse.c                                    |    6 +
>  lib/ofp-print.c                                    |    6 +
>  lib/ofp-util.c                                     |   37 ++++-
>  lib/ofp-util.def                                   |    1 +
>  lib/packets.c                                      |   15 +-
>  lib/packets.h                                      |    9 +-
>  ofproto/ofproto-dpif.c                             |   51 +++++-
>  tests/odp.at                                       |   36 ++++-
>  tests/ofp-print.at                                 |    4 +-
>  tests/ofproto-dpif.at                              |  182 ++++++++++++--------
>  tests/ofproto.at                                   |   10 +-
>  utilities/ovs-dpctl.c                              |    2 +
>  utilities/ovs-ofctl.8.in                           |   18 ++
>  44 files changed, 1177 insertions(+), 235 deletions(-)
> 
> diff --git a/datapath/actions.c b/datapath/actions.c
> index 8fdcb3b..c6f2b6a 100644
> --- a/datapath/actions.c
> +++ b/datapath/actions.c
> @@ -82,8 +82,9 @@ static int pop_vlan(struct sk_buff *skb)
>  	if (likely(vlan_tx_tag_present(skb))) {
>  		vlan_set_tci(skb, 0);
>  	} else {
> -		if (unlikely(skb->protocol != htons(ETH_P_8021Q) ||
> -			     skb->len < VLAN_ETH_HLEN))
> +		if (unlikely((skb->protocol != htons(ETH_P_8021Q) &&
> +					  skb->protocol != htons(ETH_P_8021AD)) ||
> +					  skb->len < VLAN_ETH_HLEN))
>  			return 0;
>  
>  		err = __pop_vlan_tci(skb, &tci);
> @@ -91,8 +92,9 @@ static int pop_vlan(struct sk_buff *skb)
>  			return err;
>  	}
>  	/* move next vlan tag to hw accel tag */
> -	if (likely(skb->protocol != htons(ETH_P_8021Q) ||
> -		   skb->len < VLAN_ETH_HLEN))
> +	if (likely((skb->protocol != htons(ETH_P_8021Q) &&
> +				skb->protocol != htons(ETH_P_8021AD)) ||
> +				skb->len < VLAN_ETH_HLEN))
>  		return 0;
>  
>  	err = __pop_vlan_tci(skb, &tci);
> @@ -111,8 +113,13 @@ static int push_vlan(struct sk_buff *skb, const struct ovs_action_push_vlan *vla
>  		/* push down current VLAN tag */
>  		current_tag = vlan_tx_tag_get(skb);
>  
> -		if (!__vlan_put_tag(skb, current_tag))
> -			return -ENOMEM;
> +		if (skb->protocol == htons(ETH_P_8021AD)) {
> +			if (!__vlan_put_qinq_tag(skb, current_tag))
> +				return -ENOMEM;
> +		} else {
> +			if (!__vlan_put_tag(skb, current_tag))
> +				return -ENOMEM;
> +		}
>  
>  		if (get_ip_summed(skb) == OVS_CSUM_COMPLETE)
>  			skb->csum = csum_add(skb->csum, csum_partial(skb->data
> @@ -120,6 +127,7 @@ static int push_vlan(struct sk_buff *skb, const struct ovs_action_push_vlan *vla
>  
>  	}
>  	__vlan_hwaccel_put_tag(skb, ntohs(vlan->vlan_tci) & ~VLAN_TAG_PRESENT);
> +	vlan_set_tpid(skb, vlan->vlan_tpid);
>  	return 0;
>  }
>  
> @@ -137,7 +145,8 @@ static char *get_mpls_hdr(const struct sk_buff *skb)
>  		dl_type = eth->h_proto;
>  
>  	/* Check for a VLAN tag. */
> -	while (dl_type == htons(ETH_P_8021Q) &&
> +	while ((dl_type == htons(ETH_P_8021Q) ||
> +			dl_type == htons(ETH_P_8021AD)) &&
>  			skb->len >= nh_ofs + sizeof(struct vlan_hdr)) {
>  		struct vlan_hdr *vh = (struct vlan_hdr*)(skb->data + nh_ofs);
>  		dl_type = vh->h_vlan_encapsulated_proto;
> @@ -161,7 +170,8 @@ static char *get_next_mpls_hdr(const struct sk_buff *skb)
>  		dl_type = eth->h_proto;
>  
>  	/* Check for a VLAN tag. */
> -	while (dl_type == htons(ETH_P_8021Q) &&
> +	while ((dl_type == htons(ETH_P_8021Q) ||
> +			dl_type == htons(ETH_P_8021AD)) &&
>  			skb->len >= nh_ofs + sizeof(struct vlan_hdr)) {
>  		struct vlan_hdr *vh = (struct vlan_hdr*)(skb->data + nh_ofs);
>  		dl_type = vh->h_vlan_encapsulated_proto;
> @@ -183,7 +193,8 @@ static __be16 get_ethertype(struct sk_buff *skb)
>  	struct ethhdr *eth = eth_hdr(skb);
>  	__be16 eth_type = htons(0);
>  	if (likely(ntohs(eth->h_proto) >= ETH_TYPE_MIN)) {
> -		if (eth->h_proto == htons(ETH_P_8021Q)) {
> +		if (eth->h_proto == htons(ETH_P_8021Q) ||
> +			eth->h_proto == htons(ETH_P_8021AD)) {
>  			eth_type = *(__be16 *)(get_mpls_hdr(skb) - 2);
>  			return eth_type;
>  		} else {
> @@ -199,7 +210,8 @@ static void set_ethertype(struct sk_buff *skb, __be16 eth_type)
>  {
>  	struct ethhdr *eth = eth_hdr(skb);
>  	if (likely(ntohs(eth->h_proto) >= ETH_TYPE_MIN)) {
> -		if (eth->h_proto != htons(ETH_P_8021Q)) {
> +		if (eth->h_proto != htons(ETH_P_8021Q) &&
> +			eth->h_proto != htons(ETH_P_8021AD)) {
>  			skb->protocol = eth->h_proto = eth_type;
>          } else {
>  			/* 2 bytes before L2.5(MPLS) or L3 header is the
> diff --git a/datapath/datapath.c b/datapath/datapath.c
> index 766b8f4..c5daaea 100644
> --- a/datapath/datapath.c
> +++ b/datapath/datapath.c
> @@ -466,6 +466,11 @@ static int queue_userspace_packet(struct net *net, int dp_ifindex,
>  		if (err)
>  			return err;
>  
> +		if (vlan_tx_qinq_tag_present(skb)) {
> +			err = vlan_deaccel_qinq_tag(nskb);
> +			if (err)
> +				return err;
> +		}
>  		skb = nskb;
>  	}
>  
> @@ -705,7 +710,8 @@ static int validate_actions(const struct nlattr *attr,
>  
>  		case OVS_ACTION_ATTR_PUSH_VLAN:
>  			vlan = nla_data(a);
> -			if (vlan->vlan_tpid != htons(ETH_P_8021Q))
> +			if (vlan->vlan_tpid != htons(ETH_P_8021Q) &&
> +				vlan->vlan_tpid != htons(ETH_P_8021AD))
>  				return -EINVAL;
>  			if (!(vlan->vlan_tci & htons(VLAN_TAG_PRESENT)))
>  				return -EINVAL;
> diff --git a/datapath/datapath.h b/datapath/datapath.h
> index affbf0e..feb7107 100644
> --- a/datapath/datapath.h
> +++ b/datapath/datapath.h
> @@ -115,6 +115,8 @@ struct ovs_skb_cb {
>  #ifdef NEED_VLAN_FIELD
>  	u16			vlan_tci;
>  #endif
> +	__be16		vlan_tpid;
> +	u16			vlan_qinq_tci;
>  };
>  #define OVS_CB(skb) ((struct ovs_skb_cb *)(skb)->cb)
>  
> diff --git a/datapath/flow.c b/datapath/flow.c
> index d4d2a58..a0585b7 100644
> --- a/datapath/flow.c
> +++ b/datapath/flow.c
> @@ -460,7 +460,7 @@ void ovs_flow_deferred_free_acts(struct sw_flow_actions *sf_acts)
>  static int parse_vlan(struct sk_buff *skb, struct sw_flow_key *key)
>  {
>  	struct qtag_prefix {
> -		__be16 eth_type; /* ETH_P_8021Q */
> +		__be16 eth_type; /* ETH_P_8021Q or ETH_P_8021AD. */
>  		__be16 tci;
>  	};
>  	struct qtag_prefix *qp;
> @@ -474,6 +474,29 @@ static int parse_vlan(struct sk_buff *skb, struct sw_flow_key *key)
>  
>  	qp = (struct qtag_prefix *) skb->data;
>  	key->eth.tci = qp->tci | htons(VLAN_TAG_PRESENT);
> +	key->vlan.type = qp->eth_type;
> +	__skb_pull(skb, sizeof(struct qtag_prefix));
> +
> +	return 0;
> +}
> +
> +static int parse_vlan_qinq(struct sk_buff *skb, struct sw_flow_key *key)
> +{
> +	struct qtag_prefix {
> +		__be16 eth_type; /* ETH_P_8021Q */
> +		__be16 tci;
> +	};
> +	struct qtag_prefix *qp;
> +
> +	if (unlikely(skb->len < sizeof(struct qtag_prefix) + sizeof(__be16)))
> +		return 0;
> +
> +	if (unlikely(!pskb_may_pull(skb, sizeof(struct qtag_prefix) +
> +					 sizeof(__be16))))
> +		return -ENOMEM;
> +
> +	qp = (struct qtag_prefix *) skb->data;
> +	key->vlan.qinq_tci = qp->tci | htons(VLAN_TAG_PRESENT);
>  	__skb_pull(skb, sizeof(struct qtag_prefix));
>  
>  	return 0;
> @@ -664,11 +687,37 @@ int ovs_flow_extract(struct sk_buff *skb, u16 in_port, struct sw_flow_key *key,
>  
>  	__skb_pull(skb, 2 * ETH_ALEN);
>  
> -	if (vlan_tx_tag_present(skb))
> +	if (vlan_tx_tag_present(skb)) {
>  		key->eth.tci = htons(vlan_get_tci(skb));
> -	else if (eth->h_proto == htons(ETH_P_8021Q))
> +		key->vlan.type = skb->protocol;
> +		if (vlan_tx_qinq_tag_present(skb)) {
> +			key->vlan.qinq_tci = htons(vlan_get_qinq_tci(skb));
> +			key_len = SW_FLOW_KEY_OFFSET(vlan.qinq_tci);
> +		} else {
> +			/* Find next tpid if present. */
> +			__be16 proto;
> +			proto = *(__be16 *) skb->data;
> +			if (proto == htons(ETH_P_8021Q)) {
> +				if (unlikely(parse_vlan_qinq(skb, key)))
> +					return -ENOMEM;
> +				key_len = SW_FLOW_KEY_OFFSET(vlan.qinq_tci);
> +			}
> +		}
> +	} else if (eth->h_proto == htons(ETH_P_8021Q) ||
> +			   eth->h_proto == htons(ETH_P_8021AD)) {
>  		if (unlikely(parse_vlan(skb, key)))
>  			return -ENOMEM;
> +		else {
> +			/* Find next tpid if present. */
> +			__be16 proto;
> +			proto = *(__be16 *) skb->data;
> +			if (proto == htons(ETH_P_8021Q)) {
> +				if (unlikely(parse_vlan_qinq(skb, key)))
> +					return -ENOMEM;
> +				key_len = SW_FLOW_KEY_OFFSET(vlan.qinq_tci);
> +			}
> +		}
> +	}
>  
>  	key->eth.type = parse_ethertype(skb);
>  	if (unlikely(key->eth.type == htons(0)))
> @@ -873,6 +922,7 @@ const int ovs_key_lens[OVS_KEY_ATTR_MAX + 1] = {
>  	[OVS_KEY_ATTR_ARP] = sizeof(struct ovs_key_arp),
>  	[OVS_KEY_ATTR_ND] = sizeof(struct ovs_key_nd),
>  	[OVS_KEY_ATTR_MPLS] = sizeof(__be32),
> +	[OVS_KEY_ATTR_VLAN_QINQ] = sizeof(__be16),
>  
>  	/* Not upstream. */
>  	[OVS_KEY_ATTR_TUN_ID] = sizeof(__be64),
> @@ -1066,20 +1116,39 @@ int ovs_flow_from_nlattrs(struct sw_flow_key *swkey, int *key_lenp,
>  	memcpy(swkey->eth.dst, eth_key->eth_dst, ETH_ALEN);
>  
>  	if (attrs & (1u << OVS_KEY_ATTR_ETHERTYPE) &&
> -	    nla_get_be16(a[OVS_KEY_ATTR_ETHERTYPE]) == htons(ETH_P_8021Q)) {
> +	    (nla_get_be16(a[OVS_KEY_ATTR_ETHERTYPE]) == htons(ETH_P_8021Q) ||
> +		 nla_get_be16(a[OVS_KEY_ATTR_ETHERTYPE]) == htons(ETH_P_8021AD))) {
>  		const struct nlattr *encap;
> -		__be16 tci;
> +		__be16 tci, qinq_tci;
> +		__be16 tpid;
>  
> -		if (attrs != ((1 << OVS_KEY_ATTR_VLAN) |
> -			      (1 << OVS_KEY_ATTR_ETHERTYPE) |
> -			      (1 << OVS_KEY_ATTR_ENCAP)))
> +		if (!(attrs & ((1 << OVS_KEY_ATTR_VLAN) |
> +			           (1 << OVS_KEY_ATTR_ETHERTYPE) |
> +					   (1 << OVS_KEY_ATTR_ENCAP))))
>  			return -EINVAL;
>  
>  		encap = a[OVS_KEY_ATTR_ENCAP];
>  		tci = nla_get_be16(a[OVS_KEY_ATTR_VLAN]);
> +		tpid = nla_get_be16(a[OVS_KEY_ATTR_ETHERTYPE]);
>  		if (tci & htons(VLAN_TAG_PRESENT)) {
>  			swkey->eth.tci = tci;
> -
> +			swkey->vlan.type = tpid;
> +
> +			/* Handle vlan qinq if present. */
> +			if (attrs & (1 << OVS_KEY_ATTR_VLAN_QINQ)) {
> +				qinq_tci = nla_get_be16(a[OVS_KEY_ATTR_VLAN_QINQ]);
> +				if (qinq_tci & htons(VLAN_TAG_PRESENT)) {
> +					swkey->vlan.qinq_tci = qinq_tci;
> +				} else if (!qinq_tci) {
> +					/* Corner case for truncated 802.1AD header. */
> +					*key_lenp = key_len;
> +					return 0;
> +				} else {
> +					return -EINVAL;
> +				}
> +				attrs &= ~(1 << OVS_KEY_ATTR_VLAN_QINQ);
> +				key_len = SW_FLOW_KEY_OFFSET(vlan.qinq_tci);
> +			}
>  			err = parse_flow_nlattrs(encap, a, &attrs);
>  			if (err)
>  				return err;
> @@ -1088,7 +1157,7 @@ int ovs_flow_from_nlattrs(struct sw_flow_key *swkey, int *key_lenp,
>  			if (nla_len(encap))
>  				return -EINVAL;
>  
> -			swkey->eth.type = htons(ETH_P_8021Q);
> +			swkey->eth.type = tpid;
>  			*key_lenp = key_len;
>  			return 0;
>  		} else {
> @@ -1270,10 +1339,21 @@ int ovs_flow_to_nlattrs(const struct sw_flow_key *swkey, struct sk_buff *skb)
>  	memcpy(eth_key->eth_src, swkey->eth.src, ETH_ALEN);
>  	memcpy(eth_key->eth_dst, swkey->eth.dst, ETH_ALEN);
>  
> -	if (swkey->eth.tci || swkey->eth.type == htons(ETH_P_8021Q)) {
> -		if (nla_put_be16(skb, OVS_KEY_ATTR_ETHERTYPE, htons(ETH_P_8021Q)) ||
> -		    nla_put_be16(skb, OVS_KEY_ATTR_VLAN, swkey->eth.tci))
> -			goto nla_put_failure;
> +	if (swkey->eth.tci ||
> +		swkey->eth.type == htons(ETH_P_8021Q) ||
> +		swkey->vlan.qinq_tci) {
> +		if (swkey->vlan.qinq_tci) {
> +			if (nla_put_be16(skb, OVS_KEY_ATTR_ETHERTYPE, swkey->vlan.type) ||
> +				nla_put_be16(skb, OVS_KEY_ATTR_VLAN, swkey->eth.tci) ||
> +				nla_put_be16(skb, OVS_KEY_ATTR_VLAN_QINQ, swkey->vlan.qinq_tci)) {
> +				goto nla_put_failure;
> +			}
> +		} else {
> +			if (nla_put_be16(skb, OVS_KEY_ATTR_ETHERTYPE, htons(ETH_P_8021Q)) ||
> +				nla_put_be16(skb, OVS_KEY_ATTR_VLAN, swkey->eth.tci)) {
> +				goto nla_put_failure;
> +			}
> +		}
>  		encap = nla_nest_start(skb, OVS_KEY_ATTR_ENCAP);
>  		if (!swkey->eth.tci)
>  			goto unencap;
> diff --git a/datapath/flow.h b/datapath/flow.h
> index f0d040f..21591d7 100644
> --- a/datapath/flow.h
> +++ b/datapath/flow.h
> @@ -53,6 +53,10 @@ struct sw_flow_key {
>  		__be16 type;		/* Ethernet frame type. */
>  	} eth;
>  	struct {
> +		__be16 type;		/* VLAN qinq outer frame type either 0x8100 or 0x88a8. */
> +		__be16 qinq_tci;	/* 0 if no VLAN, VLAN_TAG_PRESENT set otherwise. */
> +	} vlan;
> +	struct {
>  		__be32 mpls_lse;	/* 0 if no MPLS, otherwise MPLS Label Stack entry */
>  	} mpls;
>  	struct {
> diff --git a/datapath/linux/compat/include/linux/if_vlan.h b/datapath/linux/compat/include/linux/if_vlan.h
> index dc4b15e..ded61b9 100644
> --- a/datapath/linux/compat/include/linux/if_vlan.h
> +++ b/datapath/linux/compat/include/linux/if_vlan.h
> @@ -5,6 +5,10 @@
>  #include <linux/version.h>
>  #include_next <linux/if_vlan.h>
>  
> +#ifndef                ETH_P_8021AD
> +#define                ETH_P_8021AD            0x88a8
> +#endif
> +
>  /*
>   * The behavior of __vlan_put_tag() has changed over time:
>   *
> @@ -45,6 +49,39 @@ static inline struct sk_buff *__vlan_put_tag(struct sk_buff *skb, u16 vlan_tci)
>  	return skb;
>  }
>  
> +/*
> + * The behavior of __vlan_put_qinq_tag() is similar to __vlan_put_tag()
> + * it adds new vlan tpid 0x88a8 as an outer tag.
> + * Works for kernel version < 2.6.39.
> + * for kernel > 2.6.39, similar function needs to be added to upstream
> + * kernel.
> + *
> + */
> +static inline struct sk_buff *
> +__vlan_put_qinq_tag(struct sk_buff *skb, u16 vlan_tci)
> +{
> +	struct vlan_ethhdr *veth;
> +
> +	if (skb_cow_head(skb, VLAN_HLEN) < 0) {
> +		kfree_skb(skb);
> +		return NULL;
> +	}
> +	veth = (struct vlan_ethhdr *)skb_push(skb, VLAN_HLEN);
> +
> +	/* Move the mac addresses to the beginning of the new header. */
> +	memmove(skb->data, skb->data + VLAN_HLEN, 2 * VLAN_ETH_ALEN);
> +	skb->mac_header -= VLAN_HLEN;
> +
> +	/* first, the ethernet type */
> +	veth->h_vlan_proto = htons(ETH_P_8021AD);
> +
> +	/* now, the TCI */
> +	veth->h_vlan_TCI = htons(vlan_tci);
> +
> +	skb->protocol = htons(ETH_P_8021AD);
> +
> +	return skb;
> +}
>  
>  /* All of these were introduced in a single commit preceding 2.6.33, so
>   * presumably all of them or none of them are present. */
> diff --git a/datapath/linux/compat/include/linux/netfilter_bridge.h b/datapath/linux/compat/include/linux/netfilter_bridge.h
> index c526537..65884c8 100644
> --- a/datapath/linux/compat/include/linux/netfilter_bridge.h
> +++ b/datapath/linux/compat/include/linux/netfilter_bridge.h
> @@ -13,6 +13,7 @@ static inline unsigned int nf_bridge_encap_header_len(const struct sk_buff *skb)
>  {
>  	switch (skb->protocol) {
>  	case __constant_htons(ETH_P_8021Q):
> +	case __constant_htons(ETH_P_8021AD):
>  		return VLAN_HLEN;
>  	default:
>  		return 0;
> diff --git a/datapath/linux/compat/netdevice.c b/datapath/linux/compat/netdevice.c
> index 9e92eeb..b5ae776 100644
> --- a/datapath/linux/compat/netdevice.c
> +++ b/datapath/linux/compat/netdevice.c
> @@ -52,7 +52,8 @@ u32 rpl_netif_skb_features(struct sk_buff *skb)
>  	__be16 protocol = skb->protocol;
>  	u32 features = skb->dev->features;
>  
> -	if (protocol == htons(ETH_P_8021Q)) {
> +	if (protocol == htons(ETH_P_8021Q) ||
> +		protocol == htons(ETH_P_8021AD)) {
>  		struct vlan_ethhdr *veh = (struct vlan_ethhdr *)skb->data;
>  		protocol = veh->h_vlan_encapsulated_proto;
>  	} else if (!vlan_tx_tag_present(skb)) {
> @@ -61,7 +62,8 @@ u32 rpl_netif_skb_features(struct sk_buff *skb)
>  
>  	features &= (vlan_features | NETIF_F_HW_VLAN_TX);
>  
> -	if (protocol != htons(ETH_P_8021Q)) {
> +	if (protocol != htons(ETH_P_8021Q) &&
> +		protocol != htons(ETH_P_8021AD)) {
>  		return harmonize_features(skb, protocol, features);
>  	} else {
>  		features &= NETIF_F_SG | NETIF_F_HIGHDMA | NETIF_F_FRAGLIST |
> @@ -77,7 +79,8 @@ struct sk_buff *rpl_skb_gso_segment(struct sk_buff *skb, u32 features)
>  	__be16 skb_proto;
>  	struct sk_buff *skb_gso;
>  
> -	while (type == htons(ETH_P_8021Q)) {
> +	while (type == htons(ETH_P_8021Q) ||
> +		   type == htons(ETH_P_8021AD)) {
>  		struct vlan_hdr *vh;
>  
>  		if (unlikely(!pskb_may_pull(skb, vlan_depth + VLAN_HLEN)))
> diff --git a/datapath/tunnel.c b/datapath/tunnel.c
> index 3128f6e..b95a1bd 100644
> --- a/datapath/tunnel.c
> +++ b/datapath/tunnel.c
> @@ -401,27 +401,38 @@ static __be16 check_skb_mpls(struct sk_buff *skb, __be16 protocol, u8 *err)
>  	return protocol;
>  }
>  
> -/* Check VLAN/MPLS packets. */
> -static __be16 check_skb_vlan_mpls(struct sk_buff *skb, __be16 protocol, u8 *err)
> +/* Check for VLAN-QinQ/MPLS or VLAN/MPLS encapsulation. */
> +static __be16 check_skb_vlan_qinq_mpls(struct sk_buff *skb, __be16 protocol, u8 *err)
>  {
>  	__be32 mpls_lse = htonl(0);
>  	unsigned int mpls_lse_len = MPLS_HLEN;
> +	unsigned int vlan_hlen = 0;
>  
> -	/* Handle VLAN/MPLS encapsulated packets. */
> -	if (protocol == htons(ETH_P_MPLS_UC) ||
> -		protocol == htons(ETH_P_MPLS_MC)) {
> -		if (unlikely(!pskb_may_pull(skb, MPLS_HLEN))) {
> +	/* Handle VLAN-QinQ/MPLS encapsulated packets. */
> +	if (protocol == htons(ETH_P_8021Q)) {
> +
> +		if (unlikely(!pskb_may_pull(skb, VLAN_HLEN + mpls_lse_len))) {
>  			*err = 0;
>  			return protocol;
>  		}
>  
> -		mpls_lse = *(__be32 *)(skb->data + VLAN_ETH_HLEN);
> +		skb_set_network_header(skb, skb_network_offset(skb) + VLAN_HLEN);
> +		protocol = *(__be16 *)(skb->data + VLAN_ETH_HLEN + 2);
> +		vlan_hlen += VLAN_ETH_HLEN;
> +		mpls_lse_len = 0;
> +	}
> +
> +	if (protocol == htons(ETH_P_MPLS_UC) ||
> +		protocol == htons(ETH_P_MPLS_MC)) {
> +
> +		mpls_lse = *(__be32 *)(skb->data + vlan_hlen + mpls_lse_len);
> +		mpls_lse_len = MPLS_HLEN;
>  		while (!(mpls_lse & htonl(MPLS_STACK_MASK))) {
>  			if (unlikely(!pskb_may_pull(skb, mpls_lse_len))) {
>  				*err = 0;
>  				return protocol;
>  			}
> -			mpls_lse = *(__be32 *)(skb->data + VLAN_ETH_HLEN + mpls_lse_len);
> +			mpls_lse = *(__be32 *)(skb->data + vlan_hlen + mpls_lse_len);
>  			mpls_lse_len += MPLS_HLEN;
>  		}
>  		skb_set_network_header(skb, skb_network_offset(skb) + mpls_lse_len);
> @@ -443,15 +454,16 @@ static void ecn_decapsulate(struct sk_buff *skb, u8 tos)
>  
>  		skb_set_network_header(skb, ETH_HLEN);
>  
> -		if (protocol == htons(ETH_P_8021Q)) {
> +		if (protocol == htons(ETH_P_8021Q) ||
> +			protocol == htons(ETH_P_8021AD)) {
>  			if (unlikely(!pskb_may_pull(skb, VLAN_ETH_HLEN)))
>  				return;
>  
>  			protocol = vlan_eth_hdr(skb)->h_vlan_encapsulated_proto;
>  			skb_set_network_header(skb, VLAN_ETH_HLEN);
>  
> -			/* Handle VLAN/MPLS encapsulated packets. */
> -			protocol = check_skb_vlan_mpls(skb, protocol, &rc);
> +			/* Handle VLAN-QinQ/MPLS or VLAN/MPLS encapsulated packets. */
> +			protocol = check_skb_vlan_qinq_mpls(skb, protocol, &rc);
>  			if (!rc)
>  				return;
>  		} else {
> @@ -517,6 +529,7 @@ void ovs_tnl_rcv(struct vport *vport, struct sk_buff *skb, u8 tos)
>  
>  	ecn_decapsulate(skb, tos);
>  	vlan_set_tci(skb, 0);
> +	vlan_set_qinq_tci(skb, 0);
>  
>  	if (unlikely(compute_ip_summed(skb, false))) {
>  		kfree_skb(skb);
> @@ -719,7 +732,8 @@ void check_mpls_hlen(struct sk_buff *skb, unsigned int *mpls_hlen)
>  }
>  
>  /* Check VLAN/MPLS packets. Do not modify skb. */
> -void check_vlan_mpls_hlen(struct sk_buff *skb, unsigned int *mpls_hlen)
> +void check_vlan_qinq_mpls_hlen(struct sk_buff *skb, unsigned int *vlan_hlen,
> +								unsigned int *mpls_hlen)
>  {
>  	__be16 protocol;
>  	__be32 mpls_lse = htonl(0);
> @@ -739,6 +753,12 @@ void check_vlan_mpls_hlen(struct sk_buff *skb, unsigned int *mpls_hlen)
>  	}
>  
>  	protocol = *(__be16 *)(skb->data + eth_type_len);
> +	if (protocol == htons(ETH_P_8021Q)) {
> +		*vlan_hlen += VLAN_HLEN;
> +		total_hlen += VLAN_HLEN;
> +		protocol = *(__be16 *)(skb->data + eth_type_len + VLAN_HLEN);
> +	}
> +
>  	if (protocol == htons(ETH_P_MPLS_UC) ||
>  		protocol == htons(ETH_P_MPLS_MC)) {
>  		*mpls_hlen += MPLS_HLEN;
> @@ -792,10 +812,11 @@ bool ovs_tnl_frag_needed(struct vport *vport,
>  		return false;
>  
>  	/* Allocate */
> -	if (old_eh->h_proto == htons(ETH_P_8021Q)) {
> +	if (old_eh->h_proto == htons(ETH_P_8021Q) ||
> +		old_eh->h_proto == htons(ETH_P_8021AD)) {
>  
>  		eth_hdr_len = VLAN_ETH_HLEN;
> -		check_vlan_mpls_hlen(skb, &mpls_hdr_len);
> +		check_vlan_qinq_mpls_hlen(skb, &eth_hdr_len, &mpls_hdr_len);
>  	}
>  
>  	payload_length = skb->len - eth_hdr_len - mpls_hdr_len;
> @@ -827,13 +848,16 @@ bool ovs_tnl_frag_needed(struct vport *vport,
>  	memcpy(eh->h_dest, old_eh->h_source, ETH_ALEN);
>  	memcpy(eh->h_source, mutable->eth_addr, ETH_ALEN);
>  	nskb->protocol = eh->h_proto = old_eh->h_proto;
> -	if (old_eh->h_proto == htons(ETH_P_8021Q)) {
> +	if (old_eh->h_proto == htons(ETH_P_8021Q) ||
> +		old_eh->h_proto == htons(ETH_P_8021AD)) {
>  		struct vlan_ethhdr *vh = (struct vlan_ethhdr *)eh;
>  
>  		vh->h_vlan_TCI = vlan_eth_hdr(skb)->h_vlan_TCI;
>  		vh->h_vlan_encapsulated_proto = skb->protocol;
> -	} else
> +	} else {
>  		vlan_set_tci(nskb, vlan_get_tci(skb));
> +		vlan_set_qinq_tci(nskb, vlan_get_qinq_tci(skb));
> +	}
>  	skb_reset_mac_header(nskb);
>  
>  	/* Protocol */
> @@ -875,16 +899,17 @@ static bool check_mtu(struct sk_buff *skb,
>  	__be16 frag_off = mutable->flags & TNL_F_DF_DEFAULT ? htons(IP_DF) : 0;
>  	int mtu = 0;
>  	unsigned int packet_length = skb->len - ETH_HLEN;
> -	unsigned int mpls_hlen = 0;
> +	unsigned int vlan_hlen = 0, mpls_hlen = 0;
>  
> -	/* Allow for one level of tagging and allow mpls headers
> +	/* Allow for two level of tagging and allow mpls headers
>  	 * in the packet length. */
>  	if (!vlan_tx_tag_present(skb) &&
> -	    eth_hdr(skb)->h_proto == htons(ETH_P_8021Q)) {
> +	    (eth_hdr(skb)->h_proto == htons(ETH_P_8021Q) ||
> +		 eth_hdr(skb)->h_proto == htons(ETH_P_8021AD))) {
>  
>  		packet_length -= VLAN_HLEN;
> -		check_vlan_mpls_hlen(skb, &mpls_hlen);
> -		packet_length = packet_length - mpls_hlen;
> +		check_vlan_qinq_mpls_hlen(skb, &vlan_hlen, &mpls_hlen);
> +		packet_length = packet_length - vlan_hlen - mpls_hlen;
>  	}
>  
>  	check_mpls_hlen(skb, &mpls_hlen);
> @@ -897,9 +922,10 @@ static bool check_mtu(struct sk_buff *skb,
>  		 * currently is, so subtract it from the MTU.
>  		 */
>  		if (vlan_tx_tag_present(skb) ||
> -		    eth_hdr(skb)->h_proto == htons(ETH_P_8021Q)) {
> +			(eth_hdr(skb)->h_proto == htons(ETH_P_8021Q) ||
> +			 eth_hdr(skb)->h_proto == htons(ETH_P_8021AD))) {
>  			vlan_header = VLAN_HLEN;
> -			check_vlan_mpls_hlen(skb, &mpls_header);
> +			check_vlan_qinq_mpls_hlen(skb, &vlan_header, &mpls_header);
>  		}
>  
>  		check_mpls_hlen(skb, &mpls_header);
> @@ -1241,12 +1267,13 @@ static struct sk_buff *handle_offloads(struct sk_buff *skb,
>  {
>  	int min_headroom;
>  	int err;
> -	unsigned int mpls_hlen = 0;
> +	unsigned int mpls_hlen = 0, vlan_hlen = 0;
>  
> -	check_vlan_mpls_hlen(skb, &mpls_hlen);
> +	check_vlan_qinq_mpls_hlen(skb, &vlan_hlen, &mpls_hlen);
>  	min_headroom = LL_RESERVED_SPACE(rt_dst(rt).dev) + rt_dst(rt).header_len
>  			+ mutable->tunnel_hlen
>  			+ (vlan_tx_tag_present(skb) ? VLAN_HLEN : 0)
> +			+ vlan_hlen
>  			+ mpls_hlen;
>  
>  	if (skb_headroom(skb) < min_headroom || skb_header_cloned(skb)) {
> @@ -1265,23 +1292,38 @@ static struct sk_buff *handle_offloads(struct sk_buff *skb,
>  		struct sk_buff *nskb;
>  		u8 rc = 1;
>  
> -		if (mpls_tag_present(skb)) {
> +		if (mpls_tag_present(skb) || vlan_qinq_tag_present(skb)) {
>  			/* skb_gso_segment depends on skb->protocol, save and
>  			 * restore after the call. */
>  			__be16 tmp_protocol = skb->protocol;
>  
>  			skb_set_network_header(skb, ETH_HLEN);
>  
> -			if (unlikely(!pskb_may_pull(skb, ETH_HLEN)))
> -				goto error_free;
> +			if (skb->protocol == htons(ETH_P_8021Q) ||
> +				skb->protocol == htons(ETH_P_8021AD)) {
> +				if (unlikely(!pskb_may_pull(skb, VLAN_ETH_HLEN)))
> +					goto error_free;
>  
> -			/* Handle MPLS encapsulated packets. */
> -			skb->protocol = check_skb_mpls(skb, skb->protocol, &rc);
> -			if (!rc)
> -				goto error_free;
> +				skb->protocol = vlan_eth_hdr(skb)->h_vlan_encapsulated_proto;
> +				skb_set_network_header(skb, VLAN_ETH_HLEN);
> +
> +				/* Handle VLAN-QinQ/MPLS or VLAN/MPLS encapsulated packets. */
> +				skb->protocol = check_skb_vlan_qinq_mpls(skb, skb->protocol, &rc);
> +				if (!rc)
> +					goto error_free;
> +			} else {
> +				if (unlikely(!pskb_may_pull(skb, ETH_HLEN)))
> +					goto error_free;
>  
> -            nskb = skb_gso_segment(skb, 0);
> -            skb->protocol = tmp_protocol;
> +				/* Handle MPLS encapsulated packets. */
> +				skb->protocol = check_skb_mpls(skb, skb->protocol, &rc);
> +				if (!rc)
> +					goto error_free;
> +			}
> +
> +			skb_reset_network_header(skb);
> +			nskb = skb_gso_segment(skb, 0);
> +			skb->protocol = tmp_protocol;
>  		} else {
>  			nskb = skb_gso_segment(skb, 0);
>  		}
> @@ -1370,7 +1412,8 @@ int ovs_tnl_send(struct vport *vport, struct sk_buff *skb)
>  	u8 tos, rc;
>  
>  	/* Validate the protocol headers before we try to use them. */
> -	if (skb->protocol == htons(ETH_P_8021Q) &&
> +	if ((skb->protocol == htons(ETH_P_8021Q) ||
> +		 skb->protocol == htons(ETH_P_8021AD)) &&
>  	    !vlan_tx_tag_present(skb)) {
>  		if (unlikely(!pskb_may_pull(skb, VLAN_ETH_HLEN)))
>  			goto error_free;
> @@ -1378,8 +1421,8 @@ int ovs_tnl_send(struct vport *vport, struct sk_buff *skb)
>  		skb->protocol = vlan_eth_hdr(skb)->h_vlan_encapsulated_proto;
>  		skb_set_network_header(skb, VLAN_ETH_HLEN);
>  
> -		/* Handle VLAN/MPLS encapsulated packets. */
> -		skb->protocol = check_skb_vlan_mpls(skb, skb->protocol, &rc);
> +		/* Handle VLAN-QinQ/MPLS or VLAN/MPLS encapsulated packets. */
> +		skb->protocol = check_skb_vlan_qinq_mpls(skb, skb->protocol, &rc);
>  		if (!rc)
>  			goto error_free;
>  	} else {
> @@ -1479,6 +1522,9 @@ int ovs_tnl_send(struct vport *vport, struct sk_buff *skb)
>  		if (unlikely(vlan_deaccel_tag(skb)))
>  			goto next;
>  
> +		if (unlikely(vlan_deaccel_qinq_tag(skb)))
> +			goto next;
> +
>  		if (likely(cache)) {
>  			skb_push(skb, cache->len);
>  			memcpy(skb->data, get_cached_header(cache), cache->len);
> diff --git a/datapath/vlan.c b/datapath/vlan.c
> index 104ed55..ebcf965 100644
> --- a/datapath/vlan.c
> +++ b/datapath/vlan.c
> @@ -55,4 +55,46 @@ struct sk_buff *__vlan_hwaccel_put_tag(struct sk_buff *skb, u16 vlan_tci)
>  	OVS_CB(skb)->vlan_tci = vlan_tci | VLAN_TAG_PRESENT;
>  	return skb;
>  }
> +
>  #endif /* NEED_VLAN_FIELD */
> +
> +__be16 vlan_get_tpid(struct sk_buff *skb)
> +{
> +	return OVS_CB(skb)->vlan_tpid;
> +}
> +
> +void vlan_set_tpid(struct sk_buff *skb, __be16 vlan_tpid)
> +{
> +	OVS_CB(skb)->vlan_tpid = vlan_tpid;
> +}
> +
> +void vlan_copy_skb_qinq_tci(struct sk_buff *skb)
> +{
> +	OVS_CB(skb)->vlan_qinq_tci = 0;
> +}
> +
> +u16 vlan_get_qinq_tci(struct sk_buff *skb)
> +{
> +	return OVS_CB(skb)->vlan_qinq_tci;
> +}
> +
> +void vlan_set_qinq_tci(struct sk_buff *skb, u16 vlan_qinq_tci)
> +{
> +	OVS_CB(skb)->vlan_qinq_tci = vlan_qinq_tci;
> +}
> +
> +bool vlan_tx_qinq_tag_present(struct sk_buff *skb)
> +{
> +	return OVS_CB(skb)->vlan_qinq_tci & VLAN_TAG_PRESENT;
> +}
> +
> +u16 vlan_tx_qinq_tag_get(struct sk_buff *skb)
> +{
> +	return OVS_CB(skb)->vlan_qinq_tci & ~VLAN_TAG_PRESENT;
> +}
> +
> +struct sk_buff *__vlan_hwaccel_put_qinq_tag(struct sk_buff *skb, u16 vlan_qinq_tci)
> +{
> +	OVS_CB(skb)->vlan_qinq_tci = vlan_qinq_tci | VLAN_TAG_PRESENT;
> +	return skb;
> +}
> diff --git a/datapath/vlan.h b/datapath/vlan.h
> index 5d3573b..8036aa2 100644
> --- a/datapath/vlan.h
> +++ b/datapath/vlan.h
> @@ -84,17 +84,44 @@ u16 vlan_tx_tag_get(struct sk_buff *skb);
>  struct sk_buff *__vlan_hwaccel_put_tag(struct sk_buff *skb, u16 vlan_tci);
>  #endif /* NEED_VLAN_FIELD */
>  
> +__be16 vlan_get_tpid(struct sk_buff *skb);
> +void vlan_set_tpid(struct sk_buff *skb, __be16 vlan_tpid);
> +void vlan_copy_skb_qinq_tci(struct sk_buff *skb);
> +u16 vlan_get_qinq_tci(struct sk_buff *skb);
> +void vlan_set_qinq_tci(struct sk_buff *skb, u16 vlan_tci);
> +bool vlan_tx_qinq_tag_present(struct sk_buff *skb);
> +u16 vlan_tx_qinq_tag_get(struct sk_buff *skb);
> +struct sk_buff *__vlan_hwaccel_put_qinq_tag(struct sk_buff *skb, u16 vlan_tci);
> +
>  static inline int vlan_deaccel_tag(struct sk_buff *skb)
>  {
>  	if (!vlan_tx_tag_present(skb))
>  		return 0;
>  
> -	skb = __vlan_put_tag(skb, vlan_tx_tag_get(skb));
> -	if (unlikely(!skb))
> -		return -ENOMEM;
> +	if (skb->protocol == htons(ETH_P_8021AD)) {
> +		skb = __vlan_put_qinq_tag(skb, vlan_tx_tag_get(skb));
> +		if (unlikely(!skb))
> +			return -ENOMEM;
> +	} else {
> +		skb = __vlan_put_tag(skb, vlan_tx_tag_get(skb));
> +		if (unlikely(!skb))
> +			return -ENOMEM;
> +	}
>  
>  	vlan_set_tci(skb, 0);
>  	return 0;
>  }
>  
> +static inline int vlan_deaccel_qinq_tag(struct sk_buff *skb)
> +{
> +	if (!vlan_tx_qinq_tag_present(skb))
> +		return 0;
> +
> +	skb = __vlan_put_tag(skb, vlan_tx_qinq_tag_get(skb));
> +	if (unlikely(!skb))
> +		return -ENOMEM;
> +
> +	vlan_set_qinq_tci(skb, 0);
> +	return 0;
> +}
>  #endif /* vlan.h */
> diff --git a/datapath/vport-gre.c b/datapath/vport-gre.c
> index 471f66b..bf428b1 100644
> --- a/datapath/vport-gre.c
> +++ b/datapath/vport-gre.c
> @@ -253,7 +253,8 @@ static void gre_err(struct sk_buff *skb, u32 info)
>  	tot_hdr_len = tunnel_hdr_len + ETH_HLEN;
>  
>  	skb->protocol = eth_hdr(skb)->h_proto;
> -	if (skb->protocol == htons(ETH_P_8021Q)) {
> +	if (skb->protocol == htons(ETH_P_8021Q) ||
> +		skb->protocol == htons(ETH_P_8021AD)) {
>  		tot_hdr_len += VLAN_HLEN;
>  		skb->protocol = vlan_eth_hdr(skb)->h_vlan_encapsulated_proto;
>  	}
> diff --git a/datapath/vport-internal_dev.c b/datapath/vport-internal_dev.c
> index 595e23d..3383590 100644
> --- a/datapath/vport-internal_dev.c
> +++ b/datapath/vport-internal_dev.c
> @@ -298,6 +298,8 @@ static int internal_dev_recv(struct vport *vport, struct sk_buff *skb)
>  #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37)
>  	if (unlikely(vlan_deaccel_tag(skb)))
>  		return 0;
> +	if (unlikely(vlan_deaccel_qinq_tag(skb)))
> +		return 0;
>  #endif
>  
>  	len = skb->len;
> diff --git a/datapath/vport-netdev.c b/datapath/vport-netdev.c
> index d248cf6..23c7191 100644
> --- a/datapath/vport-netdev.c
> +++ b/datapath/vport-netdev.c
> @@ -47,10 +47,11 @@ MODULE_PARM_DESC(vlan_tso, "Enable TSO for VLAN packets");
>  
>  /* Currently MPLS support is not available in kernel, so use the #define
>   * to control MPLS offload capability.
> - * mpls_tso = 0, use OVS offload simulation.
> - * mpls_tso = 1, use Linux kernel generic offload code.
> - * MPLS_TSO = undefined, use Linux kernel generic offload code. */
> + * mpls_tso/vlan_qinq_tso = 0, use OVS offload simulation.
> + * mpls_tso/vlan_qinq_tso = 1, use Linux kernel generic offload code.
> + * MPLS_TSO/VLAN_QINQ_TSO = undefined, use Linux kernel generic offload code. */
>  #define  MPLS_TSO         true
> +#define  VLAN_QINQ_TSO    true
>  
>  #ifdef MPLS_TSO
>  #include <linux/module.h>
> @@ -62,6 +63,16 @@ MODULE_PARM_DESC(mpls_tso, "Enable TSO for MPLS packets");
>  #define mpls_tso true
>  #endif
>  
> +#ifdef VLAN_QINQ_TSO
> +#include <linux/module.h>
> +
> +static int vlan_qinq_tso __read_mostly;
> +module_param(vlan_qinq_tso, int, 0644);
> +MODULE_PARM_DESC(vlan_qinq_tso, "Enable TSO for VLAN QinQ packets");
> +#else
> +#define vlan_qinq_tso true
> +#endif
> +
>  static void netdev_port_receive(struct vport *vport, struct sk_buff *skb);
>  
>  #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)
> @@ -293,14 +304,15 @@ static void netdev_port_receive(struct vport *vport, struct sk_buff *skb)
>  static unsigned int packet_length(struct sk_buff *skb)
>  {
>  	unsigned int length = skb->len - ETH_HLEN;
> -	unsigned int mpls_hlen = 0;
> +	unsigned int mpls_hlen = 0, vlan_hlen = 0;
>  
> -	if (skb->protocol == htons(ETH_P_8021Q)) {
> +	if (skb->protocol == htons(ETH_P_8021Q) ||
> +		skb->protocol == htons(ETH_P_8021AD)) {
>  
>  		length -= VLAN_HLEN;
> -		/* Handle VLAN/MPLS encapsulated packets. */
> -        check_vlan_mpls_hlen(skb, &mpls_hlen);
> -		length -= mpls_hlen;
> +		/* Handle VLAN-QinQ/MPLS or VLAN/MPLS encapsulated packets. */
> +		check_vlan_qinq_mpls_hlen(skb, &vlan_hlen, &mpls_hlen);
> +		length = length - mpls_hlen - vlan_hlen;
>  	} else {
>  		/* Handle MPLS encapsulated packets. */
>  		check_mpls_hlen(skb, &mpls_hlen);
> @@ -323,6 +335,34 @@ static bool dev_supports_vlan_tx(struct net_device *dev)
>  #endif
>  }
>  
> +/* Check for VLAN QinQ header present. */
> +bool vlan_qinq_tag_present(struct sk_buff *skb)
> +{
> +#ifdef VLAN_QINQ_TSO
> +	__be16 protocol = htons(0);
> +
> +	if (unlikely(!pskb_may_pull(skb, 2 * ETH_ALEN + VLAN_HLEN)))
> +		return false;
> +
> +	if (vlan_tx_tag_present(skb)) {
> +		if (vlan_tx_qinq_tag_present(skb)) {
> +			return true;
> +		} else {
> +			protocol = *(__be16*)(skb->data + 2 * ETH_ALEN);
> +			if (protocol == htons(ETH_P_8021Q)) {
> +				return true;
> +			}
> +		}
> +	} else if (skb->protocol == htons(ETH_P_8021Q) ||
> +			   skb->protocol == htons(ETH_P_8021AD)) {
> +		protocol = *(__be16 *)(skb->data + 2 * ETH_ALEN + VLAN_HLEN);
> +		if (protocol == htons(ETH_P_8021Q))
> +			return true;
> +	}
> +#endif
> +	return false;
> +}
> +
>  /* Check for MPLS header presence. */
>  bool mpls_tag_present(struct sk_buff *skb)
>  {
> @@ -344,14 +384,15 @@ bool mpls_tag_present(struct sk_buff *skb)
>  	return false;
>  }
>  
> -/* Get protocol after MPLS or VLAN/MPLS header. */
> -void check_skb_vlan_mpls_protocol(struct sk_buff *skb)
> +/* Get protocol after MPLS or VLAN/MPLS or VLAN-QinQ/MPLS header. */
> +void check_skb_vlan_qinq_mpls_protocol(struct sk_buff *skb)
>  {
>  	int vlan_depth = ETH_HLEN;
>  	int vlan_hlen = 0;
>  
>  	/* Handle MPLS, VLAN/MPLS and VLAN-QinQ/MPLS encapsulated packets. */
> -	while (skb->protocol == htons(ETH_P_8021Q)) {
> +	while (skb->protocol == htons(ETH_P_8021Q) ||
> +		   skb->protocol == htons(ETH_P_8021AD)) {
>  		struct vlan_hdr *vh;
>  
>  		if (unlikely(!pskb_may_pull(skb, vlan_depth + VLAN_HLEN))) {
> @@ -387,8 +428,8 @@ static int netdev_send(struct vport *vport, struct sk_buff *skb)
>  {
>  	struct netdev_vport *netdev_vport = netdev_vport_priv(vport);
>  	int mtu = netdev_vport->dev->mtu;
> -	int len;
> -	bool mpls_tag;
> +	int len = 0;
> +	bool mpls_tag, vlan_qinq_tag;
>  
>  	if (unlikely(packet_length(skb) > mtu && !skb_is_gso(skb))) {
>  		net_warn_ratelimited("%s: dropped over-mtu packet: %d > %d\n",
> @@ -404,9 +445,10 @@ static int netdev_send(struct vport *vport, struct sk_buff *skb)
>  	forward_ip_summed(skb, true);
>  
>  	mpls_tag = mpls_tag_present(skb) && !mpls_tso;
> +	vlan_qinq_tag = vlan_qinq_tag_present(skb) && !vlan_qinq_tso;
>  
> -	/* Handle MPLS and VLAN packets(for kernel < 2.6.37). */
> -	if (mpls_tag ||
> +	/* Handle MPLS, VLAN QinQ and VLAN packets(for kernel < 2.6.37). */
> +	if (mpls_tag || vlan_qinq_tag ||
>  		(vlan_tx_tag_present(skb) && !dev_supports_vlan_tx(skb->dev))) {
>  		int features;
>  
> @@ -415,7 +457,7 @@ static int netdev_send(struct vport *vport, struct sk_buff *skb)
>  		if (vlan_tx_tag_present(skb) && !vlan_tso) {
>  			features &= ~(NETIF_F_TSO | NETIF_F_TSO6 |
>  						  NETIF_F_UFO | NETIF_F_FSO);
> -		} else if (mpls_tag) {
> +		} else if (mpls_tag || vlan_qinq_tag) {
>  			features &= ~(NETIF_F_TSO | NETIF_F_TSO6 |
>  						  NETIF_F_UFO | NETIF_F_FSO | NETIF_F_ALL_CSUM);
>  		}
> @@ -423,11 +465,11 @@ static int netdev_send(struct vport *vport, struct sk_buff *skb)
>  		if (netif_needs_gso(skb, features)) {
>  			struct sk_buff *nskb;
>  
> -			if (mpls_tag) {
> +			if (mpls_tag || vlan_qinq_tag) {
>  				/* skb_gso_segment depends on skb->protocol, save and
>  				 * restore after the call. */
>  				__be16 tmp_protocol = skb->protocol;
> -				check_skb_vlan_mpls_protocol(skb);
> +				check_skb_vlan_qinq_mpls_protocol(skb);
>  				nskb = skb_gso_segment(skb, features);
>  				skb->protocol = tmp_protocol;
>  			} else {
> @@ -456,12 +498,17 @@ static int netdev_send(struct vport *vport, struct sk_buff *skb)
>  				nskb = skb->next;
>  				skb->next = NULL;
>  
> -				/* VLAN packets (kernel < 2.6.37) or VLAN/MPLS packets. */
> +				/* VLAN packets (kernel < 2.6.37) or VLAN/MPLS packets
> +				 * or VLAN QinQ packets. */
>  				if (vlan_tx_tag_present(skb)) {
> -					skb = __vlan_put_tag(skb, vlan_tx_tag_get(skb));
> +					if (vlan_get_tpid(skb) == htons(ETH_P_8021AD))
> +						skb = __vlan_put_qinq_tag(skb, vlan_tx_tag_get(skb));
> +					else
> +						skb = __vlan_put_tag(skb, vlan_tx_tag_get(skb));
>  					if (likely(skb)) {
>  						len += skb->len;
>  						vlan_set_tci(skb, 0);
> +						vlan_set_qinq_tci(skb, 0);
>  						dev_queue_xmit(skb);
>  					}
>  				} else {
> @@ -476,7 +523,7 @@ static int netdev_send(struct vport *vport, struct sk_buff *skb)
>  			} while (skb);
>  
>  			return len;
> -		} else if (mpls_tag &&
> +		} else if ((mpls_tag || vlan_qinq_tag) &&
>  				   get_ip_summed(skb) == OVS_CSUM_PARTIAL) {
>  			int err;
>  			/* Linearize skb before calculating checksum. */
> @@ -488,12 +535,17 @@ static int netdev_send(struct vport *vport, struct sk_buff *skb)
>  		}
>  
>  tag:
> -		/* VLAN packets (kernel < 2.6.37) or VLAN/MPLS packets. */
> +		/* VLAN packets (kernel < 2.6.37) or VLAN/MPLS packets
> +		 * or VLAN QinQ packets. */
>  		if (vlan_tx_tag_present(skb)) {
> -			skb = __vlan_put_tag(skb, vlan_tx_tag_get(skb));
> +			if (vlan_get_tpid(skb) == htons(ETH_P_8021AD))
> +				skb = __vlan_put_qinq_tag(skb, vlan_tx_tag_get(skb));
> +			else
> +				skb = __vlan_put_tag(skb, vlan_tx_tag_get(skb));
>  			if (unlikely(!skb))
>  				return 0;
>  			vlan_set_tci(skb, 0);
> +			vlan_set_qinq_tci(skb, 0);
>  		}
>  	}
>  
> diff --git a/datapath/vport.h b/datapath/vport.h
> index 818ad93..1148aa8 100644
> --- a/datapath/vport.h
> +++ b/datapath/vport.h
> @@ -249,10 +249,12 @@ static inline struct vport *vport_from_priv(const void *priv)
>  
>  void ovs_vport_receive(struct vport *, struct sk_buff *);
>  void ovs_vport_record_error(struct vport *, enum vport_err_type err_type);
> -void check_vlan_mpls_hlen(struct sk_buff *skb, unsigned int *mpls_hlen);
> +void check_vlan_qinq_mpls_hlen(struct sk_buff *skb, unsigned int *vlan_hlen,
> +				unsigned int *mpls_hlen);
>  void check_mpls_hlen(struct sk_buff *skb, unsigned int *mpls_hlen);
> -void check_skb_vlan_mpls_protocol(struct sk_buff *skb);
> +void check_skb_vlan_qinq_mpls_protocol(struct sk_buff *skb);
>  bool mpls_tag_present(struct sk_buff *skb);
> +bool vlan_qinq_tag_present(struct sk_buff *skb);
>  
>  
>  /* List of statically compiled vport implementations.  Don't forget to also
> diff --git a/include/linux/openvswitch.h b/include/linux/openvswitch.h
> index 63436c7..5e7423d 100644
> --- a/include/linux/openvswitch.h
> +++ b/include/linux/openvswitch.h
> @@ -279,6 +279,7 @@ enum ovs_key_attr {
>  	OVS_KEY_ATTR_ARP,       /* struct ovs_key_arp */
>  	OVS_KEY_ATTR_ND,        /* struct ovs_key_nd */
>  	OVS_KEY_ATTR_MPLS,      /* be32 MPLS Label Stack Entry */
> +	OVS_KEY_ATTR_VLAN_QINQ,	/* be16 VLAN QINQ TCI */
>  	OVS_KEY_ATTR_TUN_ID = 63, /* be64 tunnel ID */
>  	__OVS_KEY_ATTR_MAX
>  };
> @@ -443,13 +444,13 @@ enum ovs_userspace_attr {
>   * @vlan_tci: Tag control identifier (TCI) to push.  The CFI bit must be set
>   * (but it will not be set in the 802.1Q header that is pushed).
>   *
> - * The @vlan_tpid value is typically %ETH_P_8021Q.  The only acceptable TPID
> - * values are those that the kernel module also parses as 802.1Q headers, to
> - * prevent %OVS_ACTION_ATTR_PUSH_VLAN followed by %OVS_ACTION_ATTR_POP_VLAN
> - * from having surprising results.
> + * The @vlan_tpid value is typically %ETH_P_8021Q or %ETH_P_8021AD.
> + * The only acceptable TPID values are those that the kernel module also
> + * parses as 802.1Q or 802.1AD headers, to prevent %OVS_ACTION_ATTR_PUSH_VLAN
> + * followed by %OVS_ACTION_ATTR_POP_VLAN from having surprising results.
>   */
>  struct ovs_action_push_vlan {
> -	__be16 vlan_tpid;	/* 802.1Q TPID. */
> +	__be16 vlan_tpid;	/* 802.1Q or 802.1AD TPID. */
>  	__be16 vlan_tci;	/* 802.1Q TCI (VLAN ID and priority). */
>  };
>  
> diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
> index 2e78c49..e309879 100644
> --- a/include/openflow/nicira-ext.h
> +++ b/include/openflow/nicira-ext.h
> @@ -346,6 +346,7 @@ enum nx_action_subtype {
>      NXAST_DEC_MPLS_TTL,         /* struct nx_action_header */
>      NXAST_PUSH_MPLS,            /* struct nx_action_push_mpls */
>      NXAST_POP_MPLS,             /* struct nx_action_pop_mpls */
> +    NXAST_PUSH_VLAN,            /* struct nx_action_push_vlan */
>  };
>  
>  /* Header for Nicira-defined actions. */
> @@ -1793,6 +1794,42 @@ OFP_ASSERT(sizeof(struct nx_action_output_reg) == 24);
>   * Masking: Not maskable. */
>  #define NXM_NX_MPLS_STACK   NXM_HEADER  (0x0001, 33, 1)
>  
> +/* The vlan_tpid in VLAN QinQ header.
> + *
> + * Prereqs: None
> + *
> + * Format: 16-bit integer
> + *
> + * Masking: Not maskable. */
> +#define NXM_NX_VLAN_TPID    NXM_HEADER  (0x0001, 34, 2)
> +
> +/* The vlan_qinq_tci in VLAN QinQ header.
> + *
> + * Prereqs: None
> + *
> + * Format: 16-bit integer
> + *
> + * Masking: Not maskable. */
> +#define NXM_NX_VLAN_QINQ_TCI      NXM_HEADER  (0x0001, 35, 2)
> +
> +/* The vlan_qinq_vid in VLAN QinQ header.
> + *
> + * Prereqs: None
> + *
> + * Format: 16-bit integer, lower 12 bits
> + *
> + * Masking: Not maskable. */
> +#define NXM_NX_VLAN_QINQ_VID      NXM_HEADER  (0x0001, 36, 2)
> +
> +/* The vlan_qinq_pcp in VLAN QinQ header.
> + *
> + * Prereqs: None
> + *
> + * Format: 8-bit integer, lower 3 bits
> + *
> + * Masking: Not maskable. */
> +#define NXM_NX_VLAN_QINQ_PCP      NXM_HEADER  (0x0001, 37, 1)
> +
>  /* ## --------------------- ## */
>  /* ## Requests and replies. ## */
>  /* ## --------------------- ## */
> @@ -2061,4 +2098,15 @@ struct nx_action_pop_mpls {
>  };
>  OFP_ASSERT(sizeof(struct nx_action_pop_mpls) == 16);
>  
> +/* Action structure for NXAST_PUSH_VLAN. */
> +struct nx_action_push_vlan {
> +    ovs_be16 type;                  /* OFPAT_PUSH_MPLS. */
> +    ovs_be16 len;                   /* Length is 8. */
> +    ovs_be32 vendor;                /* NX_VENDOR_ID. */
> +    ovs_be16 subtype;               /* NXAST_PUSH_VLAN */
> +    ovs_be16 tpid;                  /* VLAN tpid */
> +    uint8_t  pad[4];
> +};
> +OFP_ASSERT(sizeof(struct nx_action_push_vlan) == 16);
> +
>  #endif /* openflow/nicira-ext.h */
> diff --git a/lib/bond.c b/lib/bond.c
> index 54f2d0e..0724d1c 100644
> --- a/lib/bond.c
> +++ b/lib/bond.c
> @@ -533,7 +533,7 @@ bond_compose_learning_packet(struct bond *bond,
>      packet = ofpbuf_new(0);
>      compose_rarp(packet, eth_src);
>      if (vlan) {
> -        eth_push_vlan(packet, htons(vlan));
> +        eth_push_vlan(packet, htons(vlan), htons(ETH_TYPE_VLAN));
>      }
>  
>      *port_aux = slave->aux;
> diff --git a/lib/cfm.c b/lib/cfm.c
> index 41a27a0..d7c2753 100644
> --- a/lib/cfm.c
> +++ b/lib/cfm.c
> @@ -434,7 +434,7 @@ cfm_compose_ccm(struct cfm *cfm, struct ofpbuf *packet,
>  
>      if (ccm_vlan || cfm->ccm_pcp) {
>          uint16_t tci = ccm_vlan | (cfm->ccm_pcp << VLAN_PCP_SHIFT);
> -        eth_push_vlan(packet, htons(tci));
> +        eth_push_vlan(packet, htons(tci), htons(ETH_TYPE_VLAN));
>      }
>  
>      ccm = packet->l3;
> diff --git a/lib/classifier.c b/lib/classifier.c
> index d3cb9c0..b810f84 100644
> --- a/lib/classifier.c
> +++ b/lib/classifier.c
> @@ -263,6 +263,33 @@ cls_rule_set_dl_vlan_pcp(struct cls_rule *rule, uint8_t dl_vlan_pcp)
>      rule->wc.vlan_tci_mask |= htons(VLAN_CFI | VLAN_PCP_MASK);
>  }
>  
> +/* Modifies 'rule' so that it matches only packets with an outer tag of
> + * 802.1Q or 8021AD header. */
> +void
> +cls_rule_set_dl_vlan_tpid(struct cls_rule *rule, ovs_be16 dl_vlan_tpid)
> +{
> +    rule->wc.wildcards &= ~FWW_VLAN_TPID;
> +    flow_set_vlan_tpid(&rule->flow, dl_vlan_tpid);
> +}
> +
> +/* Modifies 'rule' to match only packets with an 802.1Q header whose
> + * VID equals the low 12 bits of 'dl_vlan_vid'. */
> +void
> +cls_rule_set_dl_vlan_qinq_vid(struct cls_rule *rule, ovs_be16 dl_vlan_qinq_vid)
> +{
> +    rule->wc.wildcards &= ~FWW_VLAN_QINQ_VID;
> +    flow_set_vlan_qinq_vid(&rule->flow, dl_vlan_qinq_vid);
> +}
> +
> +/* Modifies 'rule' to match only packets with an 802.1Q header whose
> + * PCP equals the low 3 bits of 'dl_vlan_pcp'. */
> +void
> +cls_rule_set_dl_vlan_qinq_pcp(struct cls_rule *rule, uint8_t dl_vlan_qinq_pcp)
> +{
> +    rule->wc.wildcards &= ~FWW_VLAN_QINQ_PCP;
> +    flow_set_vlan_qinq_pcp(&rule->flow, dl_vlan_qinq_pcp);
> +}
> +
>  /* Modifies 'rule' depending on 'mpls_label':
>   * Makes 'rule' match only packets with an MPLS header whose label equals the
>   * low 20 bits of 'mpls_label'. */
> @@ -554,7 +581,7 @@ cls_rule_format(const struct cls_rule *rule, struct ds *s)
>  
>      int i;
>  
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 13);
>  
>      if (rule->priority != OFP_DEFAULT_PRIORITY) {
>          ds_put_format(s, "priority=%d,", rule->priority);
> @@ -655,6 +682,17 @@ cls_rule_format(const struct cls_rule *rule, struct ds *s)
>                            ntohs(f->vlan_tci), ntohs(wc->vlan_tci_mask));
>          }
>      }
> +    if (!(w & FWW_VLAN_TPID)) {
> +        ds_put_format(s, "dl_vlan_tpid=0x%"PRIx16",", ntohs(f->vlan_tpid));
> +    }
> +    if (!(w & FWW_VLAN_QINQ_VID)) {
> +        ds_put_format(s, "dl_vlan_qinq_vid=%"PRIu16",",
> +                 vlan_tci_to_vid(f->vlan_qinq_tci));
> +    }
> +    if (!(w & FWW_VLAN_QINQ_PCP)) {
> +        ds_put_format(s, "dl_vlan_qinq_pcp=%d,",
> +                 vlan_tci_to_pcp(f->vlan_qinq_tci));
> +    }
>      format_eth_masked(s, "dl_src", f->dl_src, wc->dl_src_mask);
>      format_eth_masked(s, "dl_dst", f->dl_dst, wc->dl_dst_mask);
>      if (!skip_type && !(w & FWW_DL_TYPE)) {
> @@ -1233,7 +1271,7 @@ flow_equal_except(const struct flow *a, const struct flow *b,
>      const flow_wildcards_t wc = wildcards->wildcards;
>      int i;
>  
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 13);
>  
>      for (i = 0; i < FLOW_N_REGS; i++) {
>          if ((a->regs[i] ^ b->regs[i]) & wildcards->reg_masks[i]) {
> @@ -1267,6 +1305,11 @@ flow_equal_except(const struct flow *a, const struct flow *b,
>                  !((a->mpls_lse ^ b->mpls_lse) & htonl(MPLS_TC_MASK)))
>              && (wc & FWW_MPLS_STACK ||
>                  !((a->mpls_lse ^ b->mpls_lse) & htonl(MPLS_STACK_MASK)))
> +            && (wc & FWW_VLAN_TPID || a->vlan_tpid == b->vlan_tpid)
> +            && (wc & FWW_VLAN_QINQ_VID ||
> +                !((a->vlan_qinq_tci ^ b->vlan_qinq_tci) & htons(VLAN_VID_MASK)))
> +            && (wc & FWW_VLAN_QINQ_PCP ||
> +                !((a->vlan_qinq_tci ^ b->vlan_qinq_tci) & htons(VLAN_PCP_MASK)))
>              && ipv6_equal_except(&a->ipv6_src, &b->ipv6_src,
>                      &wildcards->ipv6_src_mask)
>              && ipv6_equal_except(&a->ipv6_dst, &b->ipv6_dst,
> diff --git a/lib/classifier.h b/lib/classifier.h
> index 9a38d63..7aca7db 100644
> --- a/lib/classifier.h
> +++ b/lib/classifier.h
> @@ -108,6 +108,9 @@ void cls_rule_set_any_vid(struct cls_rule *);
>  void cls_rule_set_dl_vlan(struct cls_rule *, ovs_be16);
>  void cls_rule_set_any_pcp(struct cls_rule *);
>  void cls_rule_set_dl_vlan_pcp(struct cls_rule *, uint8_t);
> +void cls_rule_set_dl_vlan_tpid(struct cls_rule *, ovs_be16);
> +void cls_rule_set_dl_vlan_qinq_vid(struct cls_rule *, ovs_be16);
> +void cls_rule_set_dl_vlan_qinq_pcp(struct cls_rule *, uint8_t);
>  void cls_rule_set_mpls_label(struct cls_rule *, ovs_be32);
>  void cls_rule_set_mpls_tc(struct cls_rule *, uint8_t);
>  void cls_rule_set_mpls_stack(struct cls_rule *, uint8_t);
> diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
> index a07e0d4..bb723b8 100644
> --- a/lib/dpif-netdev.c
> +++ b/lib/dpif-netdev.c
> @@ -1194,6 +1194,7 @@ execute_set_action(struct ofpbuf *packet, const struct nlattr *a)
>       case OVS_KEY_ATTR_ETHERTYPE:
>       case OVS_KEY_ATTR_IN_PORT:
>       case OVS_KEY_ATTR_VLAN:
> +     case OVS_KEY_ATTR_VLAN_QINQ:
>       case OVS_KEY_ATTR_MPLS:
>       case OVS_KEY_ATTR_ICMP:
>       case OVS_KEY_ATTR_ICMPV6:
> @@ -1229,7 +1230,7 @@ dp_netdev_execute_actions(struct dp_netdev *dp,
>  
>          case OVS_ACTION_ATTR_PUSH_VLAN:
>              vlan = nl_attr_get(a);
> -            eth_push_vlan(packet, vlan->vlan_tci);
> +            eth_push_vlan(packet, vlan->vlan_tci, vlan->vlan_tpid);
>              break;
>  
>          case OVS_ACTION_ATTR_POP_VLAN:
> diff --git a/lib/flow.c b/lib/flow.c
> index 8129724..1e907d4 100644
> --- a/lib/flow.c
> +++ b/lib/flow.c
> @@ -116,16 +116,48 @@ parse_mpls(struct ofpbuf *b, struct flow *flow)
>  }
>  
>  static void
> -parse_vlan(struct ofpbuf *b, struct flow *flow)
> +parse_remaining_vlans(struct ofpbuf *b)
>  {
>      struct qtag_prefix {
>          ovs_be16 eth_type;      /* ETH_TYPE_VLAN */
>          ovs_be16 tci;
>      };
> +    ovs_be16 ethtype = *((ovs_be16 *)b->data);
> +
> +    while ((ethtype == htons(ETH_TYPE_VLAN) ||
> +            ethtype == htons(ETH_TYPE_VLAN_8021AD)) &&
> +           (b->size >= sizeof(struct qtag_prefix) + sizeof(ovs_be16))) {
> +        struct qtag_prefix *qp = ofpbuf_pull(b, sizeof *qp);
> +        ethtype = qp->eth_type;
> +    }
> +}
> +
> +static void
> +parse_vlan(struct ofpbuf *b, struct flow *flow)
> +{
> +    struct qtag_prefix {
> +        ovs_be16 eth_type;      /* ETH_TYPE_VLAN or ETH_TYPE_VLAN_8021ad */
> +        ovs_be16 tci;
> +    };
>  
>      if (b->size >= sizeof(struct qtag_prefix) + sizeof(ovs_be16)) {
>          struct qtag_prefix *qp = ofpbuf_pull(b, sizeof *qp);
>          flow->vlan_tci = qp->tci | htons(VLAN_CFI);
> +        flow->vlan_tpid = qp->eth_type;
> +    }
> +}
> +
> +static void
> +parse_vlan_qinq(struct ofpbuf *b, struct flow *flow)
> +{
> +    struct qtag_prefix {
> +        ovs_be16 eth_type;      /* ETH_TYPE_VLAN */
> +        ovs_be16 tci;
> +    };
> +
> +    if (b->size >= sizeof(struct qtag_prefix) + sizeof(ovs_be16)) {
> +        struct qtag_prefix *qp = ofpbuf_pull(b, sizeof *qp);
> +        flow->vlan_qinq_tci = qp->tci | htons(VLAN_CFI);
>      }
>  }
>  
> @@ -389,8 +421,17 @@ flow_extract(struct ofpbuf *packet, uint32_t skb_priority, ovs_be64 tun_id,
>  
>      /* dl_type, vlan_tci. */
>      ofpbuf_pull(&b, ETH_ADDR_LEN * 2);
> -    if (eth->eth_type == htons(ETH_TYPE_VLAN)) {
> +    if (eth->eth_type == htons(ETH_TYPE_VLAN) ||
> +        eth->eth_type == htons(ETH_TYPE_VLAN_8021AD)) {
> +        ovs_be16 next_ethtype;
>          parse_vlan(&b, flow);
> +        /* Parse next vlan tag and skip over any other vlan tags. */
> +        next_ethtype = *((ovs_be16 *)b.data);
> +        if (next_ethtype == htons(ETH_TYPE_VLAN) ||
> +            next_ethtype == htons(ETH_TYPE_VLAN_8021AD)) {
> +            parse_vlan_qinq(&b, flow);
> +            parse_remaining_vlans(&b);
> +        }
>      }
>  
>      flow->dl_type = parse_ethertype(&b);
> @@ -484,7 +525,7 @@ flow_zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards)
>      const flow_wildcards_t wc = wildcards->wildcards;
>      int i;
>  
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 13);
>  
>      for (i = 0; i < FLOW_N_REGS; i++) {
>          flow->regs[i] &= wildcards->reg_masks[i];
> @@ -528,6 +569,16 @@ flow_zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards)
>      if (wc & FWW_MPLS_STACK) {
>          flow->mpls_lse &= ~htonl(MPLS_STACK_MASK);
>      }
> +    if (wc & FWW_VLAN_TPID) {
> +        flow->vlan_tpid = 0;
> +    }
> +    flow->vlan_qinq_tci &= ~htons(VLAN_CFI);
> +    if (wc & FWW_VLAN_QINQ_VID) {
> +        flow->vlan_qinq_tci &= ~htons(VLAN_VID_MASK);
> +    }
> +    if (wc & FWW_VLAN_QINQ_PCP) {
> +        flow->vlan_qinq_tci &= ~htons(VLAN_PCP_MASK);
> +    }
>      flow->nw_frag &= wildcards->nw_frag_mask;
>      if (wc & FWW_ARP_SHA) {
>          memset(flow->arp_sha, 0, sizeof flow->arp_sha);
> @@ -548,7 +599,7 @@ flow_zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards)
>  void
>  flow_get_metadata(const struct flow *flow, struct flow_metadata *fmd)
>  {
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 13);
>  
>      fmd->tun_id = flow->tun_id;
>      fmd->tun_id_mask = htonll(UINT64_MAX);
> @@ -585,6 +636,14 @@ flow_format(struct ds *ds, const struct flow *flow)
>      } else {
>          ds_put_char(ds, '0');
>      }
> +    ds_put_format(ds, "),qinq_tci(");
> +    if (flow->vlan_qinq_tci) {
> +        ds_put_format(ds, "vlan:%"PRIu16",pcp:%d",
> +                      vlan_tci_to_vid(flow->vlan_qinq_tci),
> +                      vlan_tci_to_pcp(flow->vlan_qinq_tci));
> +    } else {
> +        ds_put_char(ds, '0');
> +    }
>      ds_put_format(ds, ") mac("ETH_ADDR_FMT"->"ETH_ADDR_FMT
>                        ") type:%04"PRIx16,
>                    ETH_ADDR_ARGS(flow->dl_src),
> @@ -649,7 +708,7 @@ flow_print(FILE *stream, const struct flow *flow)
>  void
>  flow_wildcards_init_catchall(struct flow_wildcards *wc)
>  {
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 13);
>  
>      wc->wildcards = FWW_ALL;
>      wc->tun_id_mask = htonll(0);
> @@ -673,7 +732,7 @@ flow_wildcards_init_catchall(struct flow_wildcards *wc)
>  void
>  flow_wildcards_init_exact(struct flow_wildcards *wc)
>  {
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 13);
>  
>      wc->wildcards = 0;
>      wc->tun_id_mask = htonll(UINT64_MAX);
> @@ -699,7 +758,7 @@ flow_wildcards_is_exact(const struct flow_wildcards *wc)
>  {
>      int i;
>  
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 13);
>  
>      if (wc->wildcards
>          || wc->tun_id_mask != htonll(UINT64_MAX)
> @@ -733,7 +792,7 @@ flow_wildcards_is_catchall(const struct flow_wildcards *wc)
>  {
>      int i;
>  
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 13);
>  
>      if (wc->wildcards != FWW_ALL
>          || wc->tun_id_mask != htonll(0)
> @@ -770,7 +829,7 @@ flow_wildcards_combine(struct flow_wildcards *dst,
>  {
>      int i;
>  
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 13);
>  
>      dst->wildcards = src1->wildcards | src2->wildcards;
>      dst->tun_id_mask = src1->tun_id_mask & src2->tun_id_mask;
> @@ -811,7 +870,7 @@ flow_wildcards_equal(const struct flow_wildcards *a,
>  {
>      int i;
>  
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 13);
>  
>      if (a->wildcards != b->wildcards
>          || a->tun_id_mask != b->tun_id_mask
> @@ -847,7 +906,7 @@ flow_wildcards_has_extra(const struct flow_wildcards *a,
>      uint8_t eth_masked[ETH_ADDR_LEN];
>      struct in6_addr ipv6_masked;
>  
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 13);
>  
>      for (i = 0; i < FLOW_N_REGS; i++) {
>          if ((a->reg_masks[i] & b->reg_masks[i]) != b->reg_masks[i]) {
> @@ -908,6 +967,7 @@ flow_hash_symmetric_l4(const struct flow *flow, uint32_t basis)
>          };
>          ovs_be16 eth_type;
>          ovs_be16 vlan_tci;
> +        ovs_be16 vlan_qinq_tci;
>          ovs_be16 tp_port;
>          uint8_t eth_addr[ETH_ADDR_LEN];
>          uint8_t ip_proto;
> @@ -920,6 +980,7 @@ flow_hash_symmetric_l4(const struct flow *flow, uint32_t basis)
>          fields.eth_addr[i] = flow->dl_src[i] ^ flow->dl_dst[i];
>      }
>      fields.vlan_tci = flow->vlan_tci & htons(VLAN_VID_MASK);
> +    fields.vlan_qinq_tci = flow->vlan_qinq_tci & htons(VLAN_VID_MASK);
>      fields.eth_type = flow->dl_type;
>  
>      /* UDP source and destination port are not taken into account because they
> @@ -1020,6 +1081,38 @@ flow_set_vlan_pcp(struct flow *flow, uint8_t pcp)
>      flow->vlan_tci |= htons((pcp << VLAN_PCP_SHIFT) | VLAN_CFI);
>  }
>  
> +/* Sets the VLAN tpid (outer tag) tpid that 'flow' matches. */
> +void
> +flow_set_vlan_tpid(struct flow *flow, ovs_be16 vlan_tpid)
> +{
> +    flow->vlan_tpid = vlan_tpid;
> +}
> +
> +/* Sets the VLAN QinQ VID that 'flow' matches to 'vid'
> + * If it is in the range 0...4095, 'flow->vlan_tci' is set to match
> + * that VLAN.  Any existing PCP match is unchanged. */
> +void
> +flow_set_vlan_qinq_vid(struct flow *flow, ovs_be16 qinq_vid)
> +{
> +    if (qinq_vid == htons(OFP_VLAN_NONE)) {
> +        flow->vlan_qinq_tci = htons(0);
> +    } else {
> +        qinq_vid &= htons(VLAN_VID_MASK);
> +        flow->vlan_qinq_tci &= ~htons(VLAN_VID_MASK);
> +        flow->vlan_qinq_tci |= qinq_vid;
> +    }
> +}
> +
> +/* Sets the VLAN PCP that 'flow' matches to 'pcp', which should be in the
> + * range 0...7. */
> +void
> +flow_set_vlan_qinq_pcp(struct flow *flow, uint8_t qinq_pcp)
> +{
> +    qinq_pcp &= 0x07;
> +    flow->vlan_qinq_tci &= ~htons(VLAN_PCP_MASK);
> +    flow->vlan_qinq_tci |= htons((qinq_pcp << VLAN_PCP_SHIFT));
> +}
> +
>  /* Sets the MPLS Label that 'flow' matches to 'label', which is interpreted
>   * as an OpenFlow 1.1 "mpls_label" value. */
>  void
> @@ -1070,7 +1163,11 @@ flow_compose(struct ofpbuf *b, const struct flow *flow)
>      }
>  
>      if (flow->vlan_tci & htons(VLAN_CFI)) {
> -        eth_push_vlan(b, flow->vlan_tci);
> +        eth_push_vlan(b, flow->vlan_tci, flow->vlan_tpid);
> +    }
> +
> +    if (flow->vlan_qinq_tci & htons(VLAN_CFI)) {
> +        eth_push_vlan(b, flow->vlan_qinq_tci, htons(ETH_TYPE_VLAN));
>      }
>  
>      if (flow->dl_type == htons(ETH_TYPE_MPLS) ||
> diff --git a/lib/flow.h b/lib/flow.h
> index 5cfd456..ab5e063 100644
> --- a/lib/flow.h
> +++ b/lib/flow.h
> @@ -35,7 +35,7 @@ struct ofpbuf;
>  /* This sequence number should be incremented whenever anything involving flows
>   * or the wildcarding of flows changes.  This will cause build assertion
>   * failures in places which likely need to be updated. */
> -#define FLOW_WC_SEQ 12
> +#define FLOW_WC_SEQ 13
>  
>  #define FLOW_N_REGS 8
>  BUILD_ASSERT_DECL(FLOW_N_REGS <= NXM_NX_MAX_REGS);
> @@ -65,6 +65,8 @@ struct flow {
>      ovs_be32 mpls_lse;          /* MPLS label stack entry. */
>      uint16_t in_port;           /* OpenFlow port number of input port. */
>      ovs_be16 vlan_tci;          /* If 802.1Q, TCI | VLAN_CFI; otherwise 0. */
> +    ovs_be16 vlan_tpid;         /* If vlan qinq, TPID = outer tpid. */
> +    ovs_be16 vlan_qinq_tci;     /* If vlan qinq, qinq TCI | VLAN_CFI. */
>      ovs_be16 dl_type;           /* Ethernet frame type. */
>      ovs_be16 tp_src;            /* TCP/UDP source port. */
>      ovs_be16 tp_dst;            /* TCP/UDP destination port. */
> @@ -76,7 +78,7 @@ struct flow {
>      uint8_t arp_tha[6];         /* ARP/ND target hardware address. */
>      uint8_t nw_ttl;             /* IP TTL/Hop Limit. */
>      uint8_t nw_frag;            /* FLOW_FRAG_* flags. */
> -    uint8_t reserved[6];        /* Reserved for 64-bit packing. */
> +    uint8_t reserved[2];        /* Reserved for 64-bit packing. */
>  };
>  
>  /* Represents the metadata fields of struct flow.  The masks are used to
> @@ -94,14 +96,14 @@ struct flow_metadata {
>  
>  /* Assert that there are FLOW_SIG_SIZE bytes of significant data in "struct
>   * flow", followed by FLOW_PAD_SIZE bytes of padding. */
> -#define FLOW_SIG_SIZE (114 + FLOW_N_REGS * 4)
> -#define FLOW_PAD_SIZE 6
> +#define FLOW_SIG_SIZE (118 + FLOW_N_REGS * 4)
> +#define FLOW_PAD_SIZE 2
>  BUILD_ASSERT_DECL(offsetof(struct flow, nw_frag) == FLOW_SIG_SIZE - 1);
>  BUILD_ASSERT_DECL(sizeof(((struct flow *)0)->nw_frag) == 1);
>  BUILD_ASSERT_DECL(sizeof(struct flow) == FLOW_SIG_SIZE + FLOW_PAD_SIZE);
>  
>  /* Remember to update FLOW_WC_SEQ when changing 'struct flow'. */
> -BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 146 && FLOW_WC_SEQ == 12);
> +BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 150 && FLOW_WC_SEQ == 13);
>  
>  void flow_extract(struct ofpbuf *, uint32_t priority, ovs_be64 tun_id,
>                    uint16_t in_port, struct flow *);
> @@ -117,6 +119,9 @@ static inline size_t flow_hash(const struct flow *, uint32_t basis);
>  
>  void flow_set_vlan_vid(struct flow *, ovs_be16 vid);
>  void flow_set_vlan_pcp(struct flow *, uint8_t pcp);
> +void flow_set_vlan_tpid(struct flow *, ovs_be16 vlan_tpid);
> +void flow_set_vlan_qinq_vid(struct flow *, ovs_be16 qinq_vid);
> +void flow_set_vlan_qinq_pcp(struct flow *, uint8_t qinq_pcp);
>  
>  void flow_set_mpls_label(struct flow *flow, ovs_be32 label);
>  void flow_set_mpls_tc(struct flow *flow, uint8_t tc);
> @@ -164,10 +169,13 @@ typedef unsigned int OVS_BITWISE flow_wildcards_t;
>  #define FWW_MPLS_LABEL  ((OVS_FORCE flow_wildcards_t) (1 << 9))
>  #define FWW_MPLS_TC     ((OVS_FORCE flow_wildcards_t) (1 << 10))
>  #define FWW_MPLS_STACK  ((OVS_FORCE flow_wildcards_t) (1 << 11))
> -#define FWW_ALL         ((OVS_FORCE flow_wildcards_t) (((1 << 12)) - 1))
> +#define FWW_VLAN_TPID       ((OVS_FORCE flow_wildcards_t) (1 << 12))
> +#define FWW_VLAN_QINQ_VID   ((OVS_FORCE flow_wildcards_t) (1 << 13))
> +#define FWW_VLAN_QINQ_PCP   ((OVS_FORCE flow_wildcards_t) (1 << 14))
> +#define FWW_ALL         ((OVS_FORCE flow_wildcards_t) (((1 << 15)) - 1))
>  
>  /* Remember to update FLOW_WC_SEQ when adding or removing FWW_*. */
> -BUILD_ASSERT_DECL(FWW_ALL == ((1 << 12) - 1) && FLOW_WC_SEQ == 12);
> +BUILD_ASSERT_DECL(FWW_ALL == ((1 << 15) - 1) && FLOW_WC_SEQ == 13);
>  
>  /* Information on wildcards for a flow, as a supplement to "struct flow".
>   *
> @@ -193,7 +201,7 @@ struct flow_wildcards {
>  };
>  
>  /* Remember to update FLOW_WC_SEQ when updating struct flow_wildcards. */
> -BUILD_ASSERT_DECL(sizeof(struct flow_wildcards) == 120 && FLOW_WC_SEQ == 12);
> +BUILD_ASSERT_DECL(sizeof(struct flow_wildcards) == 120 && FLOW_WC_SEQ == 13);
>  
>  void flow_wildcards_init_catchall(struct flow_wildcards *);
>  void flow_wildcards_init_exact(struct flow_wildcards *);
> diff --git a/lib/learn.c b/lib/learn.c
> index 5478b74..e61f8fa 100644
> --- a/lib/learn.c
> +++ b/lib/learn.c
> @@ -184,7 +184,7 @@ learn_check(const struct nx_action_learn *learn, const struct flow *flow)
>                       * prerequisites.  No prerequisite depends on the value of
>                       * a field that is wider than 64 bits.  So just skip
>                       * setting it entirely. */
> -                    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
> +                    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 13);
>                  }
>              }
>          }
> diff --git a/lib/meta-flow.c b/lib/meta-flow.c
> index 655c814..505073a 100644
> --- a/lib/meta-flow.c
> +++ b/lib/meta-flow.c
> @@ -169,6 +169,38 @@ static const struct mf_field mf_fields[MFF_N_IDS] = {
>      },
>  
>      /* ## ---- ## */
> +    /* ## QinQ ## */
> +    /* ## ---- ## */
> +    {
> +        MFF_VLAN_TPID, "dl_vlan_tpid", NULL,
> +        MF_FIELD_SIZES(be16),
> +        MFM_NONE, FWW_VLAN_TPID,
> +        MFS_HEXADECIMAL,
> +        MFP_NONE,
> +        true,
> +        NXM_NX_VLAN_TPID, "NXM_NX_VLAN_TPID",
> +        0, NULL,
> +    }, {
> +        MFF_VLAN_QINQ_VID, "dl_vlan_qinq_vid", NULL,
> +        sizeof(ovs_be16), 12,
> +        MFM_NONE, FWW_VLAN_QINQ_VID,
> +        MFS_DECIMAL,
> +        MFP_VLAN_TPID,
> +        true,
> +        NXM_NX_VLAN_QINQ_VID, "NXM_NX_VLAN_QINQ_VID",
> +        0, NULL,
> +    }, {
> +        MFF_VLAN_QINQ_PCP, "dl_vlan_qinq_pcp", NULL,
> +        1, 3,
> +        MFM_NONE, FWW_VLAN_QINQ_PCP,
> +        MFS_DECIMAL,
> +        MFP_VLAN_TPID,
> +        true,
> +        NXM_NX_VLAN_QINQ_PCP, "NXM_NX_VLAN_QINQ_PCP",
> +        0, NULL,
> +    },
> +
> +    /* ## ---- ## */
>      /* ## L2.5 ## */
>      /* ## ---- ## */
>      {
> @@ -620,6 +652,9 @@ mf_is_all_wild(const struct mf_field *mf, const struct flow_wildcards *wc)
>      case MFF_MPLS_LABEL:
>      case MFF_MPLS_TC:
>      case MFF_MPLS_STACK:
> +    case MFF_VLAN_TPID:
> +    case MFF_VLAN_QINQ_VID:
> +    case MFF_VLAN_QINQ_PCP:
>          assert(mf->fww_bit != 0);
>          return (wc->wildcards & mf->fww_bit) != 0;
>  
> @@ -731,6 +766,9 @@ mf_get_mask(const struct mf_field *mf, const struct flow_wildcards *wc,
>      case MFF_MPLS_LABEL:
>      case MFF_MPLS_TC:
>      case MFF_MPLS_STACK:
> +    case MFF_VLAN_TPID:
> +    case MFF_VLAN_QINQ_VID:
> +    case MFF_VLAN_QINQ_PCP:
>          assert(mf->fww_bit != 0);
>          memset(mask, wc->wildcards & mf->fww_bit ? 0x00 : 0xff, mf->n_bytes);
>          break;
> @@ -897,6 +935,9 @@ mf_are_prereqs_ok(const struct mf_field *mf, const struct flow *flow)
>          return flow->dl_type == htons(ETH_TYPE_IP);
>      case MFP_IPV6:
>          return flow->dl_type == htons(ETH_TYPE_IPV6);
> +    case MFP_VLAN_TPID:
> +        return (flow->vlan_tpid == htons(ETH_TYPE_VLAN) ||
> +                flow->vlan_tpid == htons(ETH_TYPE_VLAN_8021AD));
>      case MFP_MPLS:
>          return (flow->dl_type == htons(ETH_TYPE_MPLS) ||
>                  flow->dl_type == htons(ETH_TYPE_MPLS_MCAST));
> @@ -1019,6 +1060,15 @@ mf_is_value_valid(const struct mf_field *mf, const union mf_value *value)
>      case MFF_IPV6_LABEL:
>          return !(value->be32 & ~htonl(IPV6_LABEL_MASK));
>  
> +    case MFF_VLAN_TPID:
> +        return !(value->be16 & htons(0));
> +
> +    case MFF_VLAN_QINQ_VID:
> +        return !(value->be16 & htons(VLAN_CFI | VLAN_PCP_MASK));
> +
> +    case MFF_VLAN_QINQ_PCP:
> +        return !(value->u8 & ~7);
> +
>      case MFF_MPLS_LABEL:
>          return !(value->be32 & ~htonl(MPLS_LABEL_MASK >> MPLS_LABEL_SHIFT));
>  
> @@ -1103,6 +1153,18 @@ mf_get_value(const struct mf_field *mf, const struct flow *flow,
>          value->u8 = vlan_tci_to_pcp(flow->vlan_tci);
>          break;
>  
> +    case MFF_VLAN_TPID:
> +        value->be16 = flow->vlan_tpid;
> +        break;
> +
> +    case MFF_VLAN_QINQ_VID:
> +        value->be16 = flow->vlan_qinq_tci & htons(VLAN_VID_MASK);
> +        break;
> +
> +    case MFF_VLAN_QINQ_PCP:
> +        value->u8 = vlan_tci_to_pcp(flow->vlan_qinq_tci);
> +        break;
> +
>      case MFF_MPLS_LABEL:
>          value->be32 = htonl(mpls_lse_to_label(flow->mpls_lse));
>          break;
> @@ -1321,6 +1383,18 @@ mf_set_value(const struct mf_field *mf,
>          cls_rule_set_nw_ttl(rule, value->u8);
>          break;
>  
> +    case MFF_VLAN_TPID:
> +        cls_rule_set_dl_vlan_tpid(rule, value->be16);
> +        break;
> +
> +    case MFF_VLAN_QINQ_VID:
> +        cls_rule_set_dl_vlan_qinq_vid(rule, value->be16);
> +        break;
> +
> +    case MFF_VLAN_QINQ_PCP:
> +        cls_rule_set_dl_vlan_qinq_pcp(rule, value->u8);
> +        break;
> +
>      case MFF_MPLS_LABEL:
>          cls_rule_set_mpls_label(rule, value->be32);
>          break;
> @@ -1467,6 +1541,18 @@ mf_set_flow_value(const struct mf_field *mf,
>          flow_set_vlan_pcp(flow, value->u8);
>          break;
>  
> +    case MFF_VLAN_TPID:
> +        flow_set_vlan_tpid(flow, value->be16);
> +        break;
> +
> +    case MFF_VLAN_QINQ_VID:
> +        flow_set_vlan_qinq_vid(flow, value->be16);
> +        break;
> +
> +    case MFF_VLAN_QINQ_PCP:
> +        flow_set_vlan_qinq_pcp(flow, value->u8);
> +        break;
> +
>      case MFF_MPLS_LABEL:
>          flow_set_mpls_label(flow, value->be32);
>          break;
> @@ -1719,6 +1805,21 @@ mf_set_wild(const struct mf_field *mf, struct cls_rule *rule)
>          rule->flow.nw_ttl = 0;
>          break;
>  
> +    case MFF_VLAN_TPID:
> +        rule->wc.wildcards |= FWW_VLAN_TPID;
> +        rule->flow.vlan_tpid = 0;
> +        break;
> +
> +    case MFF_VLAN_QINQ_VID:
> +        rule->wc.wildcards |= FWW_VLAN_QINQ_VID;
> +        rule->flow.vlan_qinq_tci &= ~htons(VLAN_VID_MASK);
> +        break;
> +
> +    case MFF_VLAN_QINQ_PCP:
> +        rule->wc.wildcards |= FWW_VLAN_QINQ_PCP;
> +        rule->flow.vlan_qinq_tci &= ~htons(VLAN_PCP_MASK);
> +        break;
> +
>      case MFF_MPLS_LABEL:
>          rule->wc.wildcards |= FWW_MPLS_LABEL;
>          rule->flow.mpls_lse &= ~htonl(MPLS_LABEL_MASK);
> @@ -1812,6 +1913,9 @@ mf_set(const struct mf_field *mf,
>      case MFF_ETH_TYPE:
>      case MFF_VLAN_VID:
>      case MFF_VLAN_PCP:
> +    case MFF_VLAN_TPID:
> +    case MFF_VLAN_QINQ_VID:
> +    case MFF_VLAN_QINQ_PCP:
>      case MFF_IPV6_LABEL:
>      case MFF_MPLS_LABEL:
>      case MFF_MPLS_TC:
> @@ -2076,6 +2180,18 @@ mf_random_value(const struct mf_field *mf, union mf_value *value)
>          value->u8 &= 0x07;
>          break;
>  
> +    case MFF_VLAN_TPID:
> +        value->be16 &= htons(0xffff);
> +        break;
> +
> +    case MFF_VLAN_QINQ_VID:
> +        value->be16 &= htons(VLAN_VID_MASK);
> +        break;
> +
> +    case MFF_VLAN_QINQ_PCP:
> +        value->u8 &= 0x07;
> +        break;
> +
>      case MFF_MPLS_LABEL:
>          value->be32 &= htonl(MPLS_LABEL_MASK >> MPLS_LABEL_SHIFT);
>          break;
> diff --git a/lib/meta-flow.h b/lib/meta-flow.h
> index 257ee05..d1e4534 100644
> --- a/lib/meta-flow.h
> +++ b/lib/meta-flow.h
> @@ -71,6 +71,11 @@ enum mf_field_id {
>      MFF_VLAN_VID,               /* be16 */
>      MFF_VLAN_PCP,               /* u8 */
>  
> +    /* QinQ */
> +    MFF_VLAN_TPID,              /* be16 */
> +    MFF_VLAN_QINQ_VID,          /* be16 */
> +    MFF_VLAN_QINQ_PCP,          /* u8 */
> +
>      /* L2.5 */
>      MFF_MPLS_LABEL,             /* be32 */
>      MFF_MPLS_TC,                /* u8 */
> @@ -127,6 +132,7 @@ enum mf_prereqs {
>  
>      /* L2 requirements. */
>      MFP_ARP,
> +    MFP_VLAN_TPID,
>      MFP_MPLS,
>      MFP_IPV4,
>      MFP_IPV6,
> @@ -182,6 +188,8 @@ struct mf_field {
>       *
>       *     - "dl_vlan" is 2 bytes but only 12 bits.
>       *     - "dl_vlan_pcp" is 1 byte but only 3 bits.
> +     *     - "dl_vlan_qinq_vid" is 2 bytes but only 12 bits.
> +     *     - "dl_vlan_qinq_pcp" is 1 byte but only 3 bits.
>       *     - "is_frag" is 1 byte but only 2 bits.
>       *     - "ipv6_label" is 4 bytes but only 20 bits.
>       *     - "mpls_label" is 4 bytes but only 20 bits.
> diff --git a/lib/nx-match.c b/lib/nx-match.c
> index 40fbb04..46a905b 100644
> --- a/lib/nx-match.c
> +++ b/lib/nx-match.c
> @@ -487,7 +487,7 @@ nx_put_match(struct ofpbuf *b, bool oxm, const struct cls_rule *cr,
>      int match_len;
>      int i;
>  
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 13);
>  
>      /* Metadata. */
>      if (!(wc & FWW_IN_PORT)) {
> @@ -514,6 +514,20 @@ nx_put_match(struct ofpbuf *b, bool oxm, const struct cls_rule *cr,
>       * XXX missing OXM support */
>      nxm_put_16m(b, NXM_OF_VLAN_TCI, flow->vlan_tci, cr->wc.vlan_tci_mask);
>  
> +    /* 802.1AD */
> +    if (!(wc & FWW_VLAN_TPID) &&
> +       (flow->vlan_tpid == htons(ETH_TYPE_VLAN) ||
> +        flow->vlan_tpid == htons(ETH_TYPE_VLAN_8021AD))) {
> +        nxm_put_16(b, NXM_NX_VLAN_TPID, flow->vlan_tpid);
> +        if (!(wc & FWW_VLAN_QINQ_PCP)) {
> +            nxm_put_8(b, NXM_NX_VLAN_QINQ_PCP,
> +                      vlan_tci_to_pcp(flow->vlan_qinq_tci));
> +        }
> +        if (!(wc & FWW_VLAN_QINQ_VID)) {
> +            nxm_put_16(b, NXM_NX_VLAN_QINQ_VID,
> +                 htons(vlan_tci_to_vid(flow->vlan_qinq_tci)));
> +        }
> +    }
>  
>      /* MPLS. */
>      if (!(wc & FWW_DL_TYPE) &&
> diff --git a/lib/nx-match.h b/lib/nx-match.h
> index b8d194b..7be6dcd 100644
> --- a/lib/nx-match.h
> +++ b/lib/nx-match.h
> @@ -90,7 +90,7 @@ void nxm_decode(struct mf_subfield *, ovs_be32 header, ovs_be16 ofs_nbits);
>  void nxm_decode_discrete(struct mf_subfield *, ovs_be32 header,
>                           ovs_be16 ofs, ovs_be16 n_bits);
>  
> -BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
> +BUILD_ASSERT_DECL(FLOW_WC_SEQ == 13);
>  /* Upper bound on the length of an nx_match.  The longest nx_match (an
>   * IPV6 neighbor discovery message using 5 registers) would be:
>   *
> diff --git a/lib/odp-util.c b/lib/odp-util.c
> index 2dfa32f..b8c01fb 100644
> --- a/lib/odp-util.c
> +++ b/lib/odp-util.c
> @@ -103,6 +103,7 @@ ovs_key_attr_to_string(enum ovs_key_attr attr)
>      case OVS_KEY_ATTR_IN_PORT: return "in_port";
>      case OVS_KEY_ATTR_ETHERNET: return "eth";
>      case OVS_KEY_ATTR_VLAN: return "vlan";
> +    case OVS_KEY_ATTR_VLAN_QINQ: return "vlan_qinq";
>      case OVS_KEY_ATTR_ETHERTYPE: return "eth_type";
>      case OVS_KEY_ATTR_IPV4: return "ipv4";
>      case OVS_KEY_ATTR_IPV6: return "ipv6";
> @@ -333,9 +334,7 @@ format_odp_action(struct ds *ds, const struct nlattr *a)
>      case OVS_ACTION_ATTR_PUSH_VLAN:
>          vlan = nl_attr_get(a);
>          ds_put_cstr(ds, "push_vlan(");
> -        if (vlan->vlan_tpid != htons(ETH_TYPE_VLAN)) {
> -            ds_put_format(ds, "tpid=0x%04"PRIx16",", ntohs(vlan->vlan_tpid));
> -        }
> +        ds_put_format(ds, "tpid=0x%04"PRIx16",", ntohs(vlan->vlan_tpid));
>          format_vlan_tci(ds, vlan->vlan_tci);
>          ds_put_char(ds, ')');
>          break;
> @@ -725,6 +724,7 @@ odp_flow_key_attr_len(uint16_t type)
>      case OVS_KEY_ATTR_ARP: return sizeof(struct ovs_key_arp);
>      case OVS_KEY_ATTR_ND: return sizeof(struct ovs_key_nd);
>      case OVS_KEY_ATTR_MPLS: return sizeof(ovs_be32);
> +    case OVS_KEY_ATTR_VLAN_QINQ: return sizeof(ovs_be16);
>  
>      case OVS_KEY_ATTR_UNSPEC:
>      case __OVS_KEY_ATTR_MAX:
> @@ -826,6 +826,12 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds)
>          ds_put_char(ds, ')');
>          break;
>  
> +    case OVS_KEY_ATTR_VLAN_QINQ:
> +        ds_put_char(ds, '(');
> +        format_vlan_tci(ds, nl_attr_get_be16(a));
> +        ds_put_char(ds, ')');
> +        break;
> +
>      case OVS_KEY_ATTR_MPLS:
>          ds_put_char(ds, '(');
>          format_mpls_lse(ds, nl_attr_get_be32(a));
> @@ -1091,6 +1097,30 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
>      }
>  
>      {
> +        uint16_t vid;
> +        int pcp;
> +        int cfi;
> +        int n = -1;
> +
> +        if ((sscanf(s, "vlan_qinq(vid=%"SCNi16",pcp=%i)%n", &vid, &pcp, &n) > 0
> +             && n > 0)) {
> +            nl_msg_put_be16(key, OVS_KEY_ATTR_VLAN_QINQ,
> +                            htons((vid << VLAN_VID_SHIFT) |
> +                                  (pcp << VLAN_PCP_SHIFT) |
> +                                  VLAN_CFI));
> +            return n;
> +        } else if ((sscanf(s, "vlan_qinq(vid=%"SCNi16",pcp=%i,cfi=%i)%n",
> +                           &vid, &pcp, &cfi, &n) > 0
> +             && n > 0)) {
> +            nl_msg_put_be16(key, OVS_KEY_ATTR_VLAN_QINQ,
> +                            htons((vid << VLAN_VID_SHIFT) |
> +                                  (pcp << VLAN_PCP_SHIFT) |
> +                                  (cfi ? VLAN_CFI : 0)));
> +            return n;
> +        }
> +    }
> +
> +    {
>          int eth_type;
>          int n = -1;
>  
> @@ -1406,9 +1436,17 @@ odp_flow_key_from_flow(struct ofpbuf *buf, const struct flow *flow)
>      memcpy(eth_key->eth_src, flow->dl_src, ETH_ADDR_LEN);
>      memcpy(eth_key->eth_dst, flow->dl_dst, ETH_ADDR_LEN);
>  
> -    if (flow->vlan_tci != htons(0) || flow->dl_type == htons(ETH_TYPE_VLAN)) {
> -        nl_msg_put_be16(buf, OVS_KEY_ATTR_ETHERTYPE, htons(ETH_TYPE_VLAN));
> -        nl_msg_put_be16(buf, OVS_KEY_ATTR_VLAN, flow->vlan_tci);
> +    if (flow->vlan_tci != htons(0) ||
> +        flow->dl_type == htons(ETH_TYPE_VLAN) ||
> +        flow->vlan_qinq_tci != htons(0)) {
> +        if (flow->vlan_qinq_tci != htons(0)) {
> +            nl_msg_put_be16(buf, OVS_KEY_ATTR_ETHERTYPE, flow->vlan_tpid);
> +            nl_msg_put_be16(buf, OVS_KEY_ATTR_VLAN, flow->vlan_tci);
> +            nl_msg_put_be16(buf, OVS_KEY_ATTR_VLAN_QINQ, flow->vlan_qinq_tci);
> +        } else {
> +            nl_msg_put_be16(buf, OVS_KEY_ATTR_ETHERTYPE, htons(ETH_TYPE_VLAN));
> +            nl_msg_put_be16(buf, OVS_KEY_ATTR_VLAN, flow->vlan_tci);
> +        }
>          encap = nl_msg_start_nested(buf, OVS_KEY_ATTR_ENCAP);
>          if (flow->vlan_tci == htons(0)) {
>              goto unencap;
> @@ -1844,7 +1882,7 @@ parse_8021q_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
>             ? attrs[OVS_KEY_ATTR_ENCAP] : NULL);
>      enum odp_key_fitness encap_fitness;
>      enum odp_key_fitness fitness;
> -    ovs_be16 tci;
> +    ovs_be16 tci = 0, qinq_tci = 0;
>  
>      /* Calulate fitness of outer attributes. */
>      expected_attrs |= ((UINT64_C(1) << OVS_KEY_ATTR_VLAN) |
> @@ -1869,9 +1907,32 @@ parse_8021q_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
>          return ODP_FIT_ERROR;
>      }
>  
> +    /* Get the VLAN QinQ TCI value. */
> +    /* Calulate fitness of vlan qinq attribute. */
> +    if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_VLAN_QINQ)) {
> +        expected_attrs |= (UINT64_C(1) << OVS_KEY_ATTR_VLAN_QINQ);
> +        fitness = check_expectations(present_attrs, out_of_range_attr,
> +                                 expected_attrs, key, key_len);
> +
> +        qinq_tci = nl_attr_get_be16(attrs[OVS_KEY_ATTR_VLAN_QINQ]);
> +        if (qinq_tci == htons(0)) {
> +            /* Corner case for a truncated 802.1Q header. */
> +            if (fitness == ODP_FIT_PERFECT && nl_attr_get_size(encap)) {
> +                return ODP_FIT_TOO_MUCH;
> +            }
> +            return fitness;
> +        } else if (!(qinq_tci & htons(VLAN_CFI))) {
> +            VLOG_ERR_RL(&rl, "OVS_KEY_ATTR_VLAN_QINQ 0x%04"PRIx16" is nonzero "
> +                        "but CFI bit is not set", ntohs(qinq_tci));
> +            return ODP_FIT_ERROR;
> +        }
> +        flow->vlan_qinq_tci = qinq_tci;
> +    }
> +
>      /* Set vlan_tci.
>       * Remove the TPID from dl_type since it's not the real Ethertype.  */
>      flow->vlan_tci = tci;
> +    flow->vlan_tpid = flow->dl_type;
>      flow->dl_type = htons(0);
>  
>      /* Now parse the encapsulated attributes. */
> @@ -1962,7 +2023,8 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
>          return ODP_FIT_ERROR;
>      }
>  
> -    if (flow->dl_type == htons(ETH_TYPE_VLAN)) {
> +    if (flow->dl_type == htons(ETH_TYPE_VLAN) ||
> +        flow->dl_type == htons(ETH_TYPE_VLAN_8021AD)) {
>          return parse_8021q_onward(attrs, present_attrs, out_of_range_attr,
>                                    expected_attrs, flow, key, key_len);
>      }
> @@ -2057,6 +2119,36 @@ commit_set_ether_addr_action(const struct flow *flow, struct flow *base,
>  }
>  
>  static void
> +commit_vlan_qinq_action(const struct flow *flow, struct flow *base,
> +                   struct ofpbuf *odp_actions)
> +{
> +    if ((base->vlan_qinq_tci == flow->vlan_qinq_tci) &&
> +        (base->vlan_tpid == flow->vlan_tpid)) {
> +        return;
> +    }
> +
> +    if (base->vlan_qinq_tci & htons(VLAN_CFI)) {
> +        nl_msg_put_flag(odp_actions, OVS_ACTION_ATTR_POP_VLAN);
> +    }
> +
> +    if (flow->vlan_qinq_tci & htons(VLAN_CFI)) {
> +        struct ovs_action_push_vlan vlan;
> +
> +        /* For actions push_vlan:0x8100/0x88a8, followed by strip_vlan
> +         * should be a no-op.
> +         * For actions strip_vlan, followed by push_vlan:0x8100/0x88a8
> +         * new vlan header is pushed.
> +         */
> +        vlan.vlan_tpid = htons(ETH_TYPE_VLAN);
> +        vlan.vlan_tci = flow->vlan_qinq_tci;
> +        nl_msg_put_unspec(odp_actions, OVS_ACTION_ATTR_PUSH_VLAN,
> +                          &vlan, sizeof vlan);
> +    }
> +    base->vlan_tpid = flow->vlan_tpid;
> +    base->vlan_qinq_tci = flow->vlan_qinq_tci;
> +}
> +
> +static void
>  commit_vlan_action(const struct flow *flow, struct flow *base,
>                     struct ofpbuf *odp_actions)
>  {
> @@ -2071,7 +2163,16 @@ commit_vlan_action(const struct flow *flow, struct flow *base,
>      if (flow->vlan_tci & htons(VLAN_CFI)) {
>          struct ovs_action_push_vlan vlan;
>  
> -        vlan.vlan_tpid = htons(ETH_TYPE_VLAN);
> +        /* For actions push_vlan:0x8100/0x88a8, followed by strip_vlan
> +         * should be a no-op.
> +         * For actions strip_vlan, followed by push_vlan:0x8100/0x88a8
> +         * new vlan header is pushed.
> +         */
> +        if (flow->vlan_tpid == htons(ETH_TYPE_VLAN_8021AD)) {
> +            vlan.vlan_tpid = flow->vlan_tpid;
> +        } else {
> +            vlan.vlan_tpid = htons(ETH_TYPE_VLAN);
> +        }
>          vlan.vlan_tci = flow->vlan_tci;
>          nl_msg_put_unspec(odp_actions, OVS_ACTION_ATTR_PUSH_VLAN,
>                            &vlan, sizeof vlan);
> @@ -2262,6 +2363,7 @@ commit_odp_actions(const struct flow *flow, struct flow *base,
>  {
>      commit_set_tun_id_action(flow, base, odp_actions);
>      commit_set_ether_addr_action(flow, base, odp_actions);
> +    commit_vlan_qinq_action(flow, base, odp_actions);
>      commit_vlan_action(flow, base, odp_actions);
>      commit_set_nw_action(flow, base, odp_actions);
>      commit_set_port_action(flow, base, odp_actions);
> diff --git a/lib/odp-util.h b/lib/odp-util.h
> index db85c28..82c60d2 100644
> --- a/lib/odp-util.h
> +++ b/lib/odp-util.h
> @@ -84,13 +84,14 @@ int odp_actions_from_string(const char *, const struct simap *port_names,
>   *  OVS_KEY_ATTR_ETHERNET     12    --     4     16
>   *  OVS_KEY_ATTR_ETHERTYPE     2     2     4      8  (outer VLAN ethertype)
>   *  OVS_KEY_ATTR_8021Q         4    --     4      8
> + *  OVS_KEY_ATTR_8021AD        4    --     4      8
>   *  OVS_KEY_ATTR_ENCAP         0    --     4      4  (VLAN encapsulation)
>   *  OVS_KEY_ATTR_ETHERTYPE     2     2     4      8  (inner VLAN ethertype)
>   *  OVS_KEY_ATTR_IPV6         40    --     4     44
>   *  OVS_KEY_ATTR_ICMPV6        2     2     4      8
>   *  OVS_KEY_ATTR_ND           28    --     4     32
>   *  -------------------------------------------------
> - *  total                                       156
> + *  total                                       160
>   *
>   * We include some slack space in case the calculation isn't quite right or we
>   * add another field and forget to adjust this value.
> diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
> index 7fcaf13..e26f58e 100644
> --- a/lib/ofp-parse.c
> +++ b/lib/ofp-parse.c
> @@ -326,6 +326,7 @@ parse_named_action(enum ofputil_action_code code, const struct flow *flow,
>      struct nx_action_mpls_ttl *namttl;
>      struct nx_action_push_mpls *nampush;
>      struct nx_action_pop_mpls *nampop;
> +    struct nx_action_push_vlan *navpush;
>  
>      switch (code) {
>      case OFPUTIL_OFPAT10_OUTPUT:
> @@ -480,6 +481,11 @@ parse_named_action(enum ofputil_action_code code, const struct flow *flow,
>      case OFPUTIL_NXAST_CONTROLLER:
>          parse_controller(b, arg);
>          break;
> +
> +    case OFPUTIL_NXAST_PUSH_VLAN:
> +        navpush = ofputil_put_NXAST_PUSH_VLAN(b);
> +        navpush->tpid = htons(str_to_u32(arg));
> +        break;
>      }
>  }
>  
> diff --git a/lib/ofp-print.c b/lib/ofp-print.c
> index 8ab662c..de8ac36 100644
> --- a/lib/ofp-print.c
> +++ b/lib/ofp-print.c
> @@ -184,6 +184,7 @@ ofp_print_action(struct ds *s, const union ofp_action *a,
>      const struct nx_action_output_reg *naor;
>      const struct nx_action_fin_timeout *naft;
>      const struct nx_action_controller *nac;
> +    const struct nx_action_push_vlan *navpush;
>      struct mf_subfield subfield;
>      uint16_t port;
>  
> @@ -415,6 +416,11 @@ ofp_print_action(struct ds *s, const union ofp_action *a,
>          ds_put_char(s, ')');
>          break;
>  
> +   case OFPUTIL_NXAST_PUSH_VLAN:
> +        navpush = (const struct nx_action_push_vlan *) a;
> +        ds_put_format(s, "push_vlan:0x%"PRIx16, ntohs(navpush->tpid));
> +        break;
> +
>      default:
>          break;
>      }
> diff --git a/lib/ofp-util.c b/lib/ofp-util.c
> index c61efda..8f75a66 100644
> --- a/lib/ofp-util.c
> +++ b/lib/ofp-util.c
> @@ -99,7 +99,7 @@ static const flow_wildcards_t WC_INVARIANTS = 0
>  void
>  ofputil_wildcard_from_openflow(uint32_t ofpfw, struct flow_wildcards *wc)
>  {
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 13);
>  
>      /* Initialize most of rule->wc. */
>      flow_wildcards_init_catchall(wc);
> @@ -108,7 +108,8 @@ ofputil_wildcard_from_openflow(uint32_t ofpfw, struct flow_wildcards *wc)
>      /* Wildcard fields that aren't defined by ofp_match or tun_id. */
>      wc->wildcards |= (FWW_ARP_SHA | FWW_ARP_THA | FWW_NW_ECN | FWW_NW_TTL
>                        | FWW_IPV6_LABEL | FWW_MPLS_LABEL | FWW_MPLS_TC
> -                      | FWW_MPLS_STACK);
> +                      | FWW_MPLS_STACK | FWW_VLAN_TPID | FWW_VLAN_QINQ_VID
> +                      | FWW_VLAN_QINQ_PCP);
>  
>      if (ofpfw & OFPFW_NW_TOS) {
>          /* OpenFlow 1.0 defines a TOS wildcard, but it's much later in
> @@ -1179,7 +1180,7 @@ ofputil_usable_protocols(const struct cls_rule *rule)
>  {
>      const struct flow_wildcards *wc = &rule->wc;
>  
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 13);
>  
>      /* NXM and OF1.1+ supports bitwise matching on ethernet addresses. */
>      if (!eth_mask_is_exact(wc->dl_src_mask)
> @@ -1253,6 +1254,21 @@ ofputil_usable_protocols(const struct cls_rule *rule)
>          return OFPUTIL_P_NXM_ANY;
>      }
>  
> +    /* Only NXM supports matching vlan tpid */
> +    if (!(wc->wildcards & FWW_VLAN_TPID)) {
> +        return OFPUTIL_P_NXM_ANY;
> +    }
> +
> +    /* Only NXM supports matching vlan qinq vid */
> +    if (!(wc->wildcards & FWW_VLAN_QINQ_VID)) {
> +        return OFPUTIL_P_NXM_ANY;
> +    }
> +
> +    /* Only NXM supports matching vlan qinq pcp */
> +    if (!(wc->wildcards & FWW_VLAN_QINQ_PCP)) {
> +        return OFPUTIL_P_NXM_ANY;
> +    }
> +
>      /* Other formats can express this rule. */
>      return OFPUTIL_P_ANY;
>  }
> @@ -3460,7 +3476,7 @@ validate_actions(const union ofp_action *actions, size_t n_actions,
>          enum ofperr error;
>          uint16_t port;
>          int code;
> -        ovs_be16 etype;
> +        ovs_be16 etype, vtpid;
>          ovs_be32 mpls_label;
>          uint8_t mpls_tc, mpls_ttl;
>  
> @@ -3530,6 +3546,14 @@ validate_actions(const union ofp_action *actions, size_t n_actions,
>              }
>              break;
>  
> +        case OFPUTIL_NXAST_PUSH_VLAN:
> +            vtpid = ((const struct nx_action_push_vlan *) a)->tpid;
> +            if (vtpid != htons(ETH_TYPE_VLAN) &&
> +                vtpid != htons(ETH_TYPE_VLAN_8021AD)) {
> +                error = OFPERR_OFPBAC_BAD_ARGUMENT;
> +            }
> +            break;
> +
>          case OFPUTIL_OFPAT10_ENQUEUE:
>              port = ntohs(((const struct ofp_action_enqueue *) a)->port);
>              if (port >= max_ports && port != OFPP_IN_PORT
> @@ -3859,6 +3883,7 @@ ofputil_normalize_rule(struct cls_rule *rule)
>          MAY_IPV6        = 1 << 6, /* ipv6_src, ipv6_dst, ipv6_label */
>          MAY_ND_TARGET   = 1 << 7, /* nd_target */
>          MAY_MPLS        = 1 << 8, /* mpls label and tc */
> +        MAY_VLAN_QINQ   = 1 << 9, /* vlan qinq tci */
>      } may_match;
>  
>      struct flow_wildcards wc;
> @@ -3889,6 +3914,10 @@ ofputil_normalize_rule(struct cls_rule *rule)
>      } else if (rule->flow.dl_type == htons(ETH_TYPE_MPLS) ||
>                 rule->flow.dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
>          may_match = MAY_MPLS;
> +    } else if ((rule->flow.vlan_tpid == htons(ETH_TYPE_VLAN) ||
> +                rule->flow.vlan_tpid == htons(ETH_TYPE_VLAN_8021AD)) &&
> +               rule->flow.vlan_qinq_tci != htons(0)) {
> +        may_match = MAY_VLAN_QINQ;
>      } else {
>          may_match = 0;
>      }
> diff --git a/lib/ofp-util.def b/lib/ofp-util.def
> index 0380a84..c53b73c 100644
> --- a/lib/ofp-util.def
> +++ b/lib/ofp-util.def
> @@ -47,4 +47,5 @@ NXAST_ACTION(NXAST_SET_MPLS_TTL,   nx_action_mpls_ttl,     0, "set_mpls_ttl")
>  NXAST_ACTION(NXAST_DEC_MPLS_TTL,   nx_action_header,       0, "dec_mpls_ttl")
>  NXAST_ACTION(NXAST_PUSH_MPLS,      nx_action_push_mpls,    0, "push_mpls")
>  NXAST_ACTION(NXAST_POP_MPLS,       nx_action_pop_mpls,     0, "pop_mpls")
> +NXAST_ACTION(NXAST_PUSH_VLAN,      nx_action_push_vlan,    0, "push_vlan")
>  #undef NXAST_ACTION
> diff --git a/lib/packets.c b/lib/packets.c
> index ec7a35e..b5eab46 100644
> --- a/lib/packets.c
> +++ b/lib/packets.c
> @@ -159,16 +159,16 @@ compose_rarp(struct ofpbuf *b, const uint8_t eth_src[ETH_ADDR_LEN])
>   *
>   * Also sets 'packet->l2' to point to the new Ethernet header. */
>  void
> -eth_push_vlan(struct ofpbuf *packet, ovs_be16 tci)
> +eth_push_vlan(struct ofpbuf *packet, ovs_be16 tci, ovs_be16 tpid)
>  {
>      struct eth_header *eh = packet->data;
>      struct vlan_eth_header *veh;
>  
> -    /* Insert new 802.1Q header. */
> +    /* Insert new 802.1Q or 802.1AD header. */
>      struct vlan_eth_header tmp;
>      memcpy(tmp.veth_dst, eh->eth_dst, ETH_ADDR_LEN);
>      memcpy(tmp.veth_src, eh->eth_src, ETH_ADDR_LEN);
> -    tmp.veth_type = htons(ETH_TYPE_VLAN);
> +    tmp.veth_type = tpid;
>      tmp.veth_tci = tci & htons(~VLAN_CFI);
>      tmp.veth_next_type = eh->eth_type;
>  
> @@ -186,7 +186,8 @@ eth_pop_vlan(struct ofpbuf *packet)
>  {
>      struct vlan_eth_header *veh = packet->l2;
>      if (packet->size >= sizeof *veh
> -        && veh->veth_type == htons(ETH_TYPE_VLAN)) {
> +        && (veh->veth_type == htons(ETH_TYPE_VLAN) ||
> +            veh->veth_type == htons(ETH_TYPE_VLAN_8021AD))) {
>          struct eth_header tmp;
>  
>          memcpy(tmp.eth_dst, veh->veth_dst, ETH_ADDR_LEN);
> @@ -205,7 +206,8 @@ set_ethertype(struct ofpbuf *packet, ovs_be16 eth_type)
>  {
>      struct eth_header *eh = packet->data;
>  
> -    if (eh->eth_type == htons(ETH_TYPE_VLAN)) {
> +    if (eh->eth_type == htons(ETH_TYPE_VLAN) ||
> +        eh->eth_type == htons(ETH_TYPE_VLAN_8021AD)) {
>          /* ethtype for VLAN packets is at L3_offset - 2 bytes. */
>          ovs_be16 *next_ethtype;
>          next_ethtype = (ovs_be16 *)((char *)packet->l3 - 2);
> @@ -223,7 +225,8 @@ get_ethertype(struct ofpbuf *packet)
>      char *mh = packet->l2_5;
>      ovs_be16 *ethtype = NULL;
>  
> -    if (eh->eth_type == htons(ETH_TYPE_VLAN)) {
> +    if (eh->eth_type == htons(ETH_TYPE_VLAN) ||
> +        eh->eth_type == htons(ETH_TYPE_VLAN_8021AD)) {
>          if (mh != NULL) {
>              ethtype = (ovs_be16 *)(mh - 2);
>          } else {
> diff --git a/lib/packets.h b/lib/packets.h
> index 7f15629..ccc7a8a 100644
> --- a/lib/packets.h
> +++ b/lib/packets.h
> @@ -137,7 +137,7 @@ bool eth_addr_from_string(const char *, uint8_t ea[ETH_ADDR_LEN]);
>  
>  void compose_rarp(struct ofpbuf *, const uint8_t eth_src[ETH_ADDR_LEN]);
>  
> -void eth_push_vlan(struct ofpbuf *, ovs_be16 tci);
> +void eth_push_vlan(struct ofpbuf *, ovs_be16 tci, ovs_be16 tpid);
>  void eth_pop_vlan(struct ofpbuf *);
>  
>  const char *eth_from_hex(const char *hex, struct ofpbuf **packetp);
> @@ -186,12 +186,14 @@ void pop_mpls(struct ofpbuf *, ovs_be16 ethtype);
>  
>  #define ETH_TYPE_IP            0x0800
>  #define ETH_TYPE_ARP           0x0806
> -#define ETH_TYPE_VLAN          0x8100
>  #define ETH_TYPE_IPV6          0x86dd
>  #define ETH_TYPE_LACP          0x8809
>  #define ETH_TYPE_RARP          0x8035
>  #define ETH_TYPE_MPLS          0x8847
>  #define ETH_TYPE_MPLS_MCAST    0x8848
> +#define ETH_TYPE_VLAN_8021Q    0x8100
> +#define ETH_TYPE_VLAN_8021AD   0x88a8
> +#define ETH_TYPE_VLAN          ETH_TYPE_VLAN_8021Q
>  
>  /* Minimum value for an Ethernet type.  Values below this are IEEE 802.2 frame
>   * lengths. */
> @@ -292,7 +294,8 @@ BUILD_ASSERT_DECL(VLAN_HEADER_LEN == sizeof(struct vlan_header));
>  struct vlan_eth_header {
>      uint8_t veth_dst[ETH_ADDR_LEN];
>      uint8_t veth_src[ETH_ADDR_LEN];
> -    ovs_be16 veth_type;         /* Always htons(ETH_TYPE_VLAN). */
> +    ovs_be16 veth_type;         /* Always htons(ETH_TYPE_VLAN) or
> +                                          hotns(ETH_TYPE_VLAN_8021ad). */
>      ovs_be16 veth_tci;          /* Lowest 12 bits are VLAN ID. */
>      ovs_be16 veth_next_type;
>  } __attribute__((packed));
> diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
> index 592d0b4..34294d5 100644
> --- a/ofproto/ofproto-dpif.c
> +++ b/ofproto/ofproto-dpif.c
> @@ -2993,7 +2993,7 @@ ofproto_dpif_extract_flow_key(const struct ofproto_dpif *ofproto,
>               * that 'packet' is inside a Netlink attribute: pushing 4 bytes
>               * will just overwrite the 4-byte "struct nlattr", which is fine
>               * since we don't need that header anymore. */
> -            eth_push_vlan(packet, flow->vlan_tci);
> +            eth_push_vlan(packet, flow->vlan_tci, htons(ETH_TYPE_VLAN));
>          }
>  
>          /* Let the caller know that we can't reproduce 'key' from 'flow'. */
> @@ -4861,6 +4861,7 @@ compose_output_action__(struct action_xlate_ctx *ctx, uint16_t ofp_port,
>      const struct ofport_dpif *ofport = get_ofp_port(ctx->ofproto, ofp_port);
>      uint16_t odp_port = ofp_port_to_odp_port(ofp_port);
>      ovs_be16 flow_vlan_tci = ctx->flow.vlan_tci;
> +    ovs_be16 flow_vlan_qinq_tci = ctx->flow.vlan_qinq_tci;
>      ovs_be32 flow_mpls_lse = ctx->flow.mpls_lse;
>      uint8_t flow_nw_tos = ctx->flow.nw_tos;
>      uint16_t out_port;
> @@ -4896,6 +4897,7 @@ compose_output_action__(struct action_xlate_ctx *ctx, uint16_t ofp_port,
>      ctx->sflow_n_outputs++;
>      ctx->nf_output_iface = ofp_port;
>      ctx->flow.vlan_tci = flow_vlan_tci;
> +    ctx->flow.vlan_qinq_tci = flow_vlan_qinq_tci;
>      ctx->flow.mpls_lse = flow_mpls_lse;
>      ctx->flow.nw_tos = flow_nw_tos;
>  }
> @@ -5024,6 +5026,11 @@ execute_controller_action(struct action_xlate_ctx *ctx, int len,
>          struct eth_header *eh;
>  
>          eth_pop_vlan(packet);
> +        /* Handle VLAN QinQ packets. */
> +        if (ctx->flow.vlan_qinq_tci != htons(0)) {
> +            eth_pop_vlan(packet);
> +        }
> +
>          eh = packet->l2;
>  
>          /* If the Ethernet type is less than ETH_TYPE_MIN, it's likely an 802.2
> @@ -5036,7 +5043,11 @@ execute_controller_action(struct action_xlate_ctx *ctx, int len,
>          memcpy(eh->eth_dst, ctx->flow.dl_dst, sizeof eh->eth_dst);
>  
>          if (ctx->flow.vlan_tci & htons(VLAN_CFI)) {
> -            eth_push_vlan(packet, ctx->flow.vlan_tci);
> +            eth_push_vlan(packet, ctx->flow.vlan_tci, ctx->flow.vlan_tpid);
> +        }
> +
> +        if (ctx->flow.vlan_qinq_tci & htons(VLAN_CFI)) {
> +            eth_push_vlan(packet, ctx->flow.vlan_qinq_tci, htons(ETH_TYPE_VLAN));
>          }
>  
>          if (ctx->flow.mpls_lse) {
> @@ -5399,6 +5410,7 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
>          const struct nx_action_bundle *nab;
>          const struct nx_action_output_reg *naor;
>          const struct nx_action_controller *nac;
> +        const struct nx_action_push_vlan *navpush;
>          enum ofputil_action_code code;
>          ovs_be64 tun_id;
>          ovs_be32 mpls_label;
> @@ -5416,16 +5428,27 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
>          case OFPUTIL_OFPAT10_SET_VLAN_VID:
>              ctx->flow.vlan_tci &= ~htons(VLAN_VID_MASK);
>              ctx->flow.vlan_tci |= ia->vlan_vid.vlan_vid | htons(VLAN_CFI);
> +            if (ctx->flow.vlan_tpid == htons(0)) {
> +                ctx->flow.vlan_tpid = htons(ETH_TYPE_VLAN);
> +            }
>              break;
>  
>          case OFPUTIL_OFPAT10_SET_VLAN_PCP:
>              ctx->flow.vlan_tci &= ~htons(VLAN_PCP_MASK);
>              ctx->flow.vlan_tci |= htons(
>                  (ia->vlan_pcp.vlan_pcp << VLAN_PCP_SHIFT) | VLAN_CFI);
> +            if (ctx->flow.vlan_tpid == htons(0)) {
> +                ctx->flow.vlan_tpid = htons(ETH_TYPE_VLAN);
> +            }
>              break;
>  
>          case OFPUTIL_OFPAT10_STRIP_VLAN:
> -            ctx->flow.vlan_tci = htons(0);
> +            if (ctx->flow.vlan_tci != 0) {
> +                ctx->flow.vlan_tci = htons(0);
> +                ctx->flow.vlan_tpid = htons(0);
> +            } else if (ctx->flow.vlan_qinq_tci != 0) {
> +                ctx->flow.vlan_qinq_tci = htons(0);
> +            }
>              break;
>  
>          case OFPUTIL_OFPAT10_SET_DL_SRC:
> @@ -5615,6 +5638,25 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
>              execute_controller_action(ctx, ntohs(nac->max_len), nac->reason,
>                                        ntohs(nac->controller_id));
>              break;
> +
> +        case OFPUTIL_NXAST_PUSH_VLAN:
> +            if (ctx->base_flow.vlan_tci != 0) {
> +                navpush = (const struct nx_action_push_vlan *) ia;
> +                /* For actions configured as
> +                 * strip_vlan,push_vlan:0x8100/0x88a8 - Push a new vlan header.
> +                 * push_vlan:0x8100/0x88a8,strip_vlan - no-op. */
> +                ctx->flow.vlan_tpid = navpush->tpid;
> +                if (ctx->flow.vlan_tci != htons(0)) {
> +                    ctx->flow.vlan_qinq_tci = ctx->base_flow.vlan_tci;
> +                } else {
> +                    ctx->flow.vlan_tci = ctx->base_flow.vlan_tci;
> +                }
> +            } else if (ctx->flow.vlan_tci != htons(0)) {
> +                navpush = (const struct nx_action_push_vlan *) ia;
> +                ctx->flow.vlan_tpid = navpush->tpid;
> +                ctx->flow.vlan_qinq_tci = ctx->flow.vlan_tci;
> +            }
> +            break;
>          }
>      }
>  
> @@ -6233,7 +6275,8 @@ xlate_normal(struct action_xlate_ctx *ctx)
>      }
>  
>      /* Drop malformed frames. */
> -    if (ctx->flow.dl_type == htons(ETH_TYPE_VLAN) &&
> +    if ((ctx->flow.dl_type == htons(ETH_TYPE_VLAN) ||
> +         ctx->flow.dl_type == htons(ETH_TYPE_VLAN_8021AD)) &&
>          !(ctx->flow.vlan_tci & htons(VLAN_CFI))) {
>          if (ctx->packet != NULL) {
>              static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> diff --git a/tests/odp.at b/tests/odp.at
> index f22bd69..dc7b8a0 100644
> --- a/tests/odp.at
> +++ b/tests/odp.at
> @@ -52,6 +52,14 @@ s/$/)/' odp-base.txt
>   sed 's/\(eth([[^)]]*)\),*/\1,eth_type(0x8848),mpls(label=100,tc=7,ttl=64,bos=1)' odp-base.txt
>  
>   echo
> + echo '# Valid forms with VLAN QinQ (8021Q) header.'
> + sed 's/\(eth([[^)]]*)\),*/\1,eth_type(0x8100),vlan(vid=99,pcp=7),vlan_qinq(vid=99,pcp=7),encap(/s/$/)/' odp-base.txt
> +
> + echo
> + echo '# Valid forms with VLAN QinQ (8021AD) header.'
> + sed 's/\(eth([[^)]]*)\),*/\1,eth_type(0x88a8),vlan(vid=99,pcp=7),vlan_qinq(vid=99,pcp=7),encap(/s/$/)/' odp-base.txt
> +
> + echo
>   echo '# Valid forms with QoS priority.'
>   sed 's/^/priority(1234),/' odp-base.txt
>  
> @@ -62,12 +70,32 @@ s/\(eth([[^)]]*)\),*/\1,eth_type(0x8100),vlan(vid=99,pcp=7),encap(/
>  s/$/)/' odp-base.txt
>  
>   echo
> + echo '# Valid forms with tun_id and VLAN QinQ (8021Q) headers.'
> + sed 's/^/tun_id(0xfedcba9876543210),/
> +s/\(eth([[^)]]*)\),*/\1,eth_type(0x8100),vlan(vid=99,pcp=7),vlan_qinq(vid=99,pcp=7),encap(/s/$/)/' odp-base.txt
> +
> + echo
> + echo '# Valid forms with tun_id and VLAN QinQ (8021AD) headers.'
> + sed 's/^/tun_id(0xfedcba9876543210),/
> +s/\(eth([[^)]]*)\),*/\1,eth_type(0x88a8),vlan(vid=99,pcp=7),vlan_qinq(vid=99,pcp=7),encap(/s/$/)/' odp-base.txt
> +
> + echo
>   echo '# Valid forms with QOS priority, tun_id, and VLAN headers.'
>   sed 's/^/priority(1234),tun_id(0xfedcba9876543210),/
>  s/\(eth([[^)]]*)\),*/\1,eth_type(0x8100),vlan(vid=99,pcp=7),encap(/
>  s/$/)/' odp-base.txt
>  
>   echo
> + echo '# Valid forms with QOS priority, tun_id, and VLAN QinQ (8021Q) headers.'
> + sed 's/^/priority(1234),tun_id(0xfedcba9876543210),/
> +s/\(eth([[^)]]*)\),*/\1,eth_type(0x8100),vlan(vid=99,pcp=7),vlan_qinq(vid=99,pcp=7),encap(/s/$/)/' odp-base.txt
> +
> + echo
> + echo '# Valid forms with QOS priority, tun_id, and VLAN QinQ (8021AD) headers.'
> + sed 's/^/priority(1234),tun_id(0xfedcba9876543210),/
> +s/\(eth([[^)]]*)\),*/\1,eth_type(0x88a8),vlan(vid=99,pcp=7),vlan_qinq(vid=99,pcp=7),encap(/s/$/)/' odp-base.txt
> +
> + echo
>   echo '# Valid forms with IP first fragment.'
>  sed -n 's/,frag=no),/,frag=first),/p' odp-base.txt
>  
> @@ -98,10 +126,12 @@ set(udp(src=81,dst=6632))
>  set(icmp(type=1,code=2))
>  set(ipv6(src=::1,dst=::2,label=0,proto=10,tclass=0x70,hlimit=128,frag=no))
>  set(icmpv6(type=1,code=2))
> -push_vlan(vid=12,pcp=0)
> -push_vlan(vid=13,pcp=5,cfi=0)
> +push_vlan(tpid=0x8100,vid=12,pcp=0)
> +push_vlan(tpid=0x8100,vid=13,pcp=5,cfi=0)
>  push_vlan(tpid=0x9100,vid=13,pcp=5)
>  push_vlan(tpid=0x9100,vid=13,pcp=5,cfi=0)
> +push_vlan(tpid=0x88a8,vid=13,pcp=5)
> +push_vlan(tpid=0x88a8,vid=13,pcp=5,cfi=0)
>  push_mpls(eth_type=0x8847)
>  push_mpls(eth_type=0x8848)
>  mpls_lse(label=100,tc=3,ttl=64,bos=0)
> @@ -110,7 +140,7 @@ dec_mpls_ttl
>  copy_ttl_in
>  copy_ttl_out
>  pop_vlan
> -sample(sample=9.7%,actions(1,2,3,push_vlan(vid=1,pcp=2)))
> +sample(sample=9.7%,actions(1,2,3,push_vlan(tpid=0x8100,vid=1,pcp=2)))
>  ])
>  AT_CHECK_UNQUOTED([test-odp parse-actions < actions.txt], [0],
>    [`cat actions.txt`
> diff --git a/tests/ofp-print.at b/tests/ofp-print.at
> index 7b07f89..b4748c5 100644
> --- a/tests/ofp-print.at
> +++ b/tests/ofp-print.at
> @@ -278,7 +278,7 @@ c0 a8 00 02 27 2f 00 00 78 50 cc 5b 57 af 42 1e \
>  50 00 02 00 26 e8 00 00 00 00 00 00 00 00 \
>  "], [0], [dnl
>  OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=3 (via no_match) data_len=60 buffer=0x00000111
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:06) type:0800,mpls(0) proto:6 tos:0 ttl:64 ip(192.168.0.1->192.168.0.2) port(10031->0) tcp_csum:26e8
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:06) type:0800,mpls(0) proto:6 tos:0 ttl:64 ip(192.168.0.1->192.168.0.2) port(10031->0) tcp_csum:26e8
>  ])
>  AT_CLEANUP
>  
> @@ -774,7 +774,7 @@ ff ff ff ff 00 40 01 07 00 00 00 00 00 00 00 09 \
>  31 6d 00 00 00 00 00 00 00 00 \
>  "], [0], [dnl
>  NXT_PACKET_IN (xid=0x0): table_id=7 cookie=0x9 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) tcp_csum:316d
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0),qinq_tci(0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) tcp_csum:316d
>  ])
>  AT_CLEANUP
>  
> diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
> index 7936cf9..ee46d74 100644
> --- a/tests/ofproto-dpif.at
> +++ b/tests/ofproto-dpif.at
> @@ -97,7 +97,7 @@ AT_CHECK([ovs-appctl ofproto/trace br0 'in_port(1),eth(src=50:54:00:00:00:05,dst
>  OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
>  AT_CHECK([cat ofctl_monitor.log], [0], [dnl
>  NXT_PACKET_IN (xid=0x0): table_id=1 total_len=42 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via invalid_ttl) data_len=42 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800,mpls(0) proto:1 tos:0 ttl:1 ip(192.168.0.1->192.168.0.2)
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800,mpls(0) proto:1 tos:0 ttl:1 ip(192.168.0.1->192.168.0.2)
>  ])
>  OVS_VSWITCHD_STOP
>  AT_CLEANUP
> @@ -253,6 +253,8 @@ cookie=0xa dl_src=40:44:44:44:44:44 actions=push_mpls:0x8847,set_mpls_label:10,s
>  cookie=0xb dl_src=50:55:55:55:55:55 actions=set_mpls_label:1000,set_mpls_ttl:200,copy_ttl_out,controller
>  cookie=0xd dl_src=60:66:66:66:66:66 actions=pop_mpls:0x0800,controller
>  cookie=0xc dl_src=70:77:77:77:77:77 actions=push_mpls:0x8848,set_mpls_label:1000,set_mpls_tc:7,set_mpls_ttl:250,controller
> +cookie=0xe dl_src=90:99:99:99:99:99 actions=mod_vlan_vid:101,mod_vlan_pcp:5,push_vlan:0x88a8,mod_vlan_vid:201,controller
> +cookie=0xd dl_src=80:88:88:88:88:88 actions=mod_vlan_vid:100,mod_vlan_pcp:3,push_vlan:0x8100,mod_vlan_vid:200,mod_vlan_pcp:4,controller
>  ])
>  AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
>  
> @@ -266,13 +268,13 @@ done
>  OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
>  AT_CHECK([cat ofctl_monitor.log], [0], [dnl
>  OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=1 (via no_match) data_len=60 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->9) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->9) tcp_csum:0
>  dnl
>  OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=1 (via no_match) data_len=60 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->9) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->9) tcp_csum:0
>  dnl
>  OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=1 (via no_match) data_len=60 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->9) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(50:54:00:00:00:05->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->9) tcp_csum:0
>  ])
>  
>  dnl Singleton controller action.
> @@ -285,13 +287,13 @@ done
>  OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
>  AT_CHECK([cat ofctl_monitor.log], [0], [dnl
>  OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=1 (via action) data_len=60 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(10:11:11:11:11:11->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(10:11:11:11:11:11->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
>  dnl
>  OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=1 (via action) data_len=60 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(10:11:11:11:11:11->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(10:11:11:11:11:11->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
>  dnl
>  OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=1 (via action) data_len=60 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(10:11:11:11:11:11->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(10:11:11:11:11:11->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
>  ])
>  
>  dnl Modified controller action.
> @@ -304,13 +306,51 @@ done
>  OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
>  AT_CHECK([cat ofctl_monitor.log], [0], [dnl
>  OFPT_PACKET_IN (xid=0x0): total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:15,pcp:0) mac(30:33:33:33:33:33->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(vlan:15,pcp:0),qinq_tci(0) mac(30:33:33:33:33:33->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
>  dnl
>  OFPT_PACKET_IN (xid=0x0): total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:15,pcp:0) mac(30:33:33:33:33:33->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(vlan:15,pcp:0),qinq_tci(0) mac(30:33:33:33:33:33->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
>  dnl
>  OFPT_PACKET_IN (xid=0x0): total_len=64 in_port=1 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:15,pcp:0) mac(30:33:33:33:33:33->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(vlan:15,pcp:0),qinq_tci(0) mac(30:33:33:33:33:33->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
> +])
> +
> +dnl Modified vlan qinq controller action.
> +AT_CHECK([ovs-ofctl monitor br0 65534 -P nxm --detach --pidfile 2> ofctl_monitor.log])
> +
> +for i in 1 2 3; do
> +    ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=90:99:99:99:99:99,dst=50:54:00:00:00:07),eth_type(0x0800) ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no)'
> +done
> +
> +OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
> +AT_CHECK([cat ofctl_monitor.log], [0], [dnl
> +NXT_PACKET_IN (xid=0x0): cookie=0xe total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> +priority:0,tunnel:0,in_port:0000,tci(vlan:101,pcp:5),qinq_tci(vlan:201,pcp:5) mac(90:99:99:99:99:99->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) tcp_csum:0
> +dnl
> +NXT_PACKET_IN (xid=0x0): cookie=0xe total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> +priority:0,tunnel:0,in_port:0000,tci(vlan:101,pcp:5),qinq_tci(vlan:201,pcp:5) mac(90:99:99:99:99:99->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) tcp_csum:0
> +dnl
> +NXT_PACKET_IN (xid=0x0): cookie=0xe total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> +priority:0,tunnel:0,in_port:0000,tci(vlan:101,pcp:5),qinq_tci(vlan:201,pcp:5) mac(90:99:99:99:99:99->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) tcp_csum:0
> +])
> +
> +dnl Modified vlan qinq controller action.
> +AT_CHECK([ovs-ofctl monitor br0 65534 -P nxm --detach --pidfile 2> ofctl_monitor.log])
> +
> +for i in 1 2 3; do
> +    ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=80:88:88:88:88:88,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no),tcp(src=8,dst=10)'
> +done
> +
> +OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
> +AT_CHECK([cat ofctl_monitor.log], [0], [dnl
> +NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> +priority:0,tunnel:0,in_port:0000,tci(vlan:100,pcp:3),qinq_tci(vlan:200,pcp:4) mac(80:88:88:88:88:88->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
> +dnl
> +NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> +priority:0,tunnel:0,in_port:0000,tci(vlan:100,pcp:3),qinq_tci(vlan:200,pcp:4) mac(80:88:88:88:88:88->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
> +dnl
> +NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> +priority:0,tunnel:0,in_port:0000,tci(vlan:100,pcp:3),qinq_tci(vlan:200,pcp:4) mac(80:88:88:88:88:88->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->10) tcp_csum:0
>  ])
>  
>  dnl Modified mpls controller action.
> @@ -323,13 +363,13 @@ done
>  OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
>  AT_CHECK([cat ofctl_monitor.log], [0], [dnl
>  NXT_PACKET_IN (xid=0x0): cookie=0xa total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=48 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(40:44:44:44:44:44->50:54:00:00:00:07) type:8847,mpls(label:10,tc:3,ttl:64,bos:1) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(40:44:44:44:44:44->50:54:00:00:00:07) type:8847,mpls(label:10,tc:3,ttl:64,bos:1) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
>  dnl
>  NXT_PACKET_IN (xid=0x0): cookie=0xa total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=48 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(40:44:44:44:44:44->50:54:00:00:00:07) type:8847,mpls(label:10,tc:3,ttl:64,bos:1) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(40:44:44:44:44:44->50:54:00:00:00:07) type:8847,mpls(label:10,tc:3,ttl:64,bos:1) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
>  dnl
>  NXT_PACKET_IN (xid=0x0): cookie=0xa total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(40:44:44:44:44:44->50:54:00:00:00:07) type:8847,mpls(label:10,tc:3,ttl:64,bos:1) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(40:44:44:44:44:44->50:54:00:00:00:07) type:8847,mpls(label:10,tc:3,ttl:64,bos:1) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
>  ])
>  
>  dnl Modified mpls ttl action.
> @@ -342,13 +382,13 @@ done
>  OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
>  AT_CHECK([cat ofctl_monitor.log], [0], [dnl
>  NXT_PACKET_IN (xid=0x0): cookie=0xb total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(50:55:55:55:55:55->50:54:00:00:00:07) type:8847,mpls(label:1000,tc:7,ttl:200,bos:1),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no)
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(50:55:55:55:55:55->50:54:00:00:00:07) type:8847,mpls(label:1000,tc:7,ttl:200,bos:1),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no)
>  dnl
>  NXT_PACKET_IN (xid=0x0): cookie=0xb total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(50:55:55:55:55:55->50:54:00:00:00:07) type:8847,mpls(label:1000,tc:7,ttl:200,bos:1),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no)
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(50:55:55:55:55:55->50:54:00:00:00:07) type:8847,mpls(label:1000,tc:7,ttl:200,bos:1),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no)
>  dnl
>  NXT_PACKET_IN (xid=0x0): cookie=0xb total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(50:55:55:55:55:55->50:54:00:00:00:07) type:8847,mpls(label:1000,tc:7,ttl:200,bos:1),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no)
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(50:55:55:55:55:55->50:54:00:00:00:07) type:8847,mpls(label:1000,tc:7,ttl:200,bos:1),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no)
>  ])
>  
>  dnl Modified mpls ipv6 controller action.
> @@ -361,13 +401,13 @@ done
>  OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
>  AT_CHECK([cat ofctl_monitor.log], [0], [dnl
>  NXT_PACKET_IN (xid=0x0): cookie=0xc total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(70:77:77:77:77:77->50:54:00:00:00:07) type:8848,mpls(label:1000,tc:7,ttl:250,bos:1) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(70:77:77:77:77:77->50:54:00:00:00:07) type:8848,mpls(label:1000,tc:7,ttl:250,bos:1) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
>  dnl
>  NXT_PACKET_IN (xid=0x0): cookie=0xc total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(70:77:77:77:77:77->50:54:00:00:00:07) type:8848,mpls(label:1000,tc:7,ttl:250,bos:1) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(70:77:77:77:77:77->50:54:00:00:00:07) type:8848,mpls(label:1000,tc:7,ttl:250,bos:1) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
>  dnl
>  NXT_PACKET_IN (xid=0x0): cookie=0xc total_len=68 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(70:77:77:77:77:77->50:54:00:00:00:07) type:8848,mpls(label:1000,tc:7,ttl:250,bos:1) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(70:77:77:77:77:77->50:54:00:00:00:07) type:8848,mpls(label:1000,tc:7,ttl:250,bos:1) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
>  ])
>  
>  dnl Modified mpls pop action.
> @@ -380,13 +420,13 @@ done
>  OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
>  AT_CHECK([cat ofctl_monitor.log], [0], [dnl
>  NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=60 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0), mac(60:66:66:66:66:66->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2)
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(60:66:66:66:66:66->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2)
>  dnl
>  NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=60 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0), mac(60:66:66:66:66:66->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2)
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(60:66:66:66:66:66->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2)
>  dnl
>  NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=60 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=68 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0), mac(60:66:66:66:66:66->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2)
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(60:66:66:66:66:66->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2)
>  ])
>  
>  dnl Checksum TCP.
> @@ -399,31 +439,31 @@ done
>  OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
>  AT_CHECK([cat ofctl_monitor.log], [0], [dnl
>  NXT_PACKET_IN (xid=0x0): cookie=0x1 total_len=60 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=60 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x3 total_len=64 in_port=1 tun_id=0x0 reg0=0x1 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0),qinq_tci(0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=2 cookie=0x4 total_len=64 in_port=1 tun_id=0x0 reg0=0x1 reg1=0x2 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0),qinq_tci(0) mac(80:81:81:81:81:81->50:54:00:00:00:07) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=3 cookie=0x5 total_len=64 in_port=1 tun_id=0x0 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0),qinq_tci(0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) tcp_csum:0
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=4 cookie=0x6 total_len=64 in_port=1 tun_id=0x0 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->192.168.0.2) port(8->11) tcp_csum:1a03
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0),qinq_tci(0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->192.168.0.2) port(8->11) tcp_csum:1a03
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=5 cookie=0x7 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(8->11) tcp_csum:3205
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0),qinq_tci(0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(8->11) tcp_csum:3205
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=6 cookie=0x8 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->11) tcp_csum:31b8
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0),qinq_tci(0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->11) tcp_csum:31b8
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=7 cookie=0x9 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) tcp_csum:316d
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0),qinq_tci(0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) tcp_csum:316d
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=7 cookie=0x9 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) tcp_csum:316d
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0),qinq_tci(0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:6 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) tcp_csum:316d
>  ])
>  
>  dnl Checksum UDP.
> @@ -436,31 +476,31 @@ done
>  OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
>  AT_CHECK([cat ofctl_monitor.log], [0], [dnl
>  NXT_PACKET_IN (xid=0x0): cookie=0x1 total_len=60 in_port=1 tun_id=0x0 reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=60 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x3 total_len=64 in_port=1 tun_id=0x0 reg0=0x1 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0),qinq_tci(0) mac(20:22:22:22:22:22->50:54:00:00:00:07) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=2 cookie=0x4 total_len=64 in_port=1 tun_id=0x0 reg0=0x1 reg1=0x2 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->50:54:00:00:00:07) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0),qinq_tci(0) mac(80:81:81:81:81:81->50:54:00:00:00:07) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=3 cookie=0x5 total_len=64 in_port=1 tun_id=0x0 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0),qinq_tci(0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(192.168.0.1->192.168.0.2) port(8->11) udp_csum:1234
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=4 cookie=0x6 total_len=64 in_port=1 tun_id=0x0 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(83.83.83.83->192.168.0.2) port(8->11) udp_csum:2c37
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0),qinq_tci(0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(83.83.83.83->192.168.0.2) port(8->11) udp_csum:2c37
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=5 cookie=0x7 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(8->11) udp_csum:4439
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0),qinq_tci(0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(8->11) udp_csum:4439
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=6 cookie=0x8 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->11) udp_csum:43ec
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0),qinq_tci(0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->11) udp_csum:43ec
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=7 cookie=0x9 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) udp_csum:43a1
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0),qinq_tci(0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) udp_csum:43a1
>  dnl
>  NXT_PACKET_IN (xid=0x0): table_id=7 cookie=0x9 total_len=64 in_port=1 tun_id=0x6 reg0=0x1 reg1=0x2 reg2=0x3 reg3=0x4 reg4=0x5 reg5=0x0 reg6=0x0 reg7=0x0 (via action) data_len=64 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) udp_csum:43a1
> +priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0),qinq_tci(0) mac(80:81:81:81:81:81->82:82:82:82:82:82) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(83.83.83.83->84.84.84.84) port(85->86) udp_csum:43a1
>  ])
>  
>  AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
> @@ -477,6 +517,8 @@ AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
>   cookie=0xb, n_packets=3, n_bytes=180, dl_src=50:55:55:55:55:55 actions=set_mpls_label:1000,set_mpls_ttl:200,copy_ttl_out,CONTROLLER:65535
>   cookie=0xd, n_packets=3, n_bytes=180, dl_src=60:66:66:66:66:66 actions=pop_mpls:0x0800,CONTROLLER:65535
>   cookie=0xc, n_packets=3, n_bytes=180, dl_src=70:77:77:77:77:77 actions=push_mpls:0x8848,set_mpls_label:1000,set_mpls_tc:7,set_mpls_ttl:250,dec_mpls_ttl,CONTROLLER:65535
> + cookie=0xd, n_packets=3, n_bytes=180, dl_src=80:88:88:88:88:88 actions=mod_vlan_vid:100,mod_vlan_pcp:3,push_vlan:0x8100,mod_vlan_vid:200,mod_vlan_pcp:4,CONTROLLER:65535
> + cookie=0xe, n_packets=3, n_bytes=180, dl_src=90:99:99:99:99:99 actions=mod_vlan_vid:101,mod_vlan_pcp:5,push_vlan:0x88a8,mod_vlan_vid:201,CONTROLLER:65535
>   n_packets=3, n_bytes=180, dl_src=10:11:11:11:11:11 actions=CONTROLLER:65535
>  NXST_FLOW reply:
>  ])
> @@ -518,7 +560,7 @@ for tuple in \
>          "0 11   0 5,7" \
>          "0 11   1 5,7" \
>          "0 12   0 1,5,6,pop_vlan,3,4,7,8" \
> -        "0 12   1 1,5,6,pop_vlan,4,7,push_vlan(vid=0,pcp=1),3,8" \
> +        "0 12   1 1,5,6,pop_vlan,4,7,push_vlan(tpid=0x8100,vid=0,pcp=1),3,8" \
>          "1  none 0 drop" \
>          "1  0    0 drop" \
>          "1  0    1 drop" \
> @@ -527,70 +569,70 @@ for tuple in \
>          "1  11   0 drop" \
>          "1  11   1 drop" \
>          "1  12   0 0,5,6,pop_vlan,3,4,7,8" \
> -        "1  12   1 0,5,6,pop_vlan,4,7,push_vlan(vid=0,pcp=1),3,8" \
> -        "2  none 0 push_vlan(vid=10,pcp=0),0,1,5,6,7,8" \
> -        "2  0    0 pop_vlan,push_vlan(vid=10,pcp=0),0,1,5,6,7,8" \
> -        "2  0    1 pop_vlan,push_vlan(vid=10,pcp=1),0,1,5,6,7,8" \
> +        "1  12   1 0,5,6,pop_vlan,4,7,push_vlan(tpid=0x8100,vid=0,pcp=1),3,8" \
> +        "2  none 0 push_vlan(tpid=0x8100,vid=10,pcp=0),0,1,5,6,7,8" \
> +        "2  0    0 pop_vlan,push_vlan(tpid=0x8100,vid=10,pcp=0),0,1,5,6,7,8" \
> +        "2  0    1 pop_vlan,push_vlan(tpid=0x8100,vid=10,pcp=1),0,1,5,6,7,8" \
>          "2  10   0 drop" \
>          "2  10   1 drop" \
>          "2  11   0 drop" \
>          "2  11   1 drop" \
>          "2  12   0 drop" \
>          "2  12   1 drop" \
> -        "3  none 0 4,7,8,push_vlan(vid=12,pcp=0),0,1,5,6" \
> -        "3  0    0 pop_vlan,4,7,8,push_vlan(vid=12,pcp=0),0,1,5,6" \
> -        "3  0    1 8,pop_vlan,4,7,push_vlan(vid=12,pcp=1),0,1,5,6" \
> +        "3  none 0 4,7,8,push_vlan(tpid=0x8100,vid=12,pcp=0),0,1,5,6" \
> +        "3  0    0 pop_vlan,4,7,8,push_vlan(tpid=0x8100,vid=12,pcp=0),0,1,5,6" \
> +        "3  0    1 8,pop_vlan,4,7,push_vlan(tpid=0x8100,vid=12,pcp=1),0,1,5,6" \
>          "3  10   0 drop" \
>          "3  10   1 drop" \
>          "3  11   0 drop" \
>          "3  11   1 drop" \
>          "3  12   0 drop" \
>          "3  12   1 drop" \
> -        "4  none 0 3,7,8,push_vlan(vid=12,pcp=0),0,1,5,6" \
> -        "4  0    0 pop_vlan,3,7,8,push_vlan(vid=12,pcp=0),0,1,5,6" \
> -        "4  0    1 3,8,pop_vlan,7,push_vlan(vid=12,pcp=1),0,1,5,6" \
> +        "4  none 0 3,7,8,push_vlan(tpid=0x8100,vid=12,pcp=0),0,1,5,6" \
> +        "4  0    0 pop_vlan,3,7,8,push_vlan(tpid=0x8100,vid=12,pcp=0),0,1,5,6" \
> +        "4  0    1 3,8,pop_vlan,7,push_vlan(tpid=0x8100,vid=12,pcp=1),0,1,5,6" \
>          "4  10   0 drop" \
>          "4  10   1 drop" \
>          "4  11   0 drop" \
>          "4  11   1 drop" \
>          "4  12   0 drop" \
>          "4  12   1 drop" \
> -        "5  none 0 2,push_vlan(vid=10,pcp=0),0,1,6,7,8" \
> -        "5  0    0 pop_vlan,2,push_vlan(vid=10,pcp=0),0,1,6,7,8" \
> -        "5  0    1 pop_vlan,2,push_vlan(vid=10,pcp=1),0,1,6,7,8" \
> +        "5  none 0 2,push_vlan(tpid=0x8100,vid=10,pcp=0),0,1,6,7,8" \
> +        "5  0    0 pop_vlan,2,push_vlan(tpid=0x8100,vid=10,pcp=0),0,1,6,7,8" \
> +        "5  0    1 pop_vlan,2,push_vlan(tpid=0x8100,vid=10,pcp=1),0,1,6,7,8" \
>          "5  10   0 0,1,6,7,8,pop_vlan,2" \
>          "5  10   1 0,1,6,7,8,pop_vlan,2" \
>          "5  11   0 0,7" \
>          "5  11   1 0,7" \
>          "5  12   0 0,1,6,pop_vlan,3,4,7,8" \
> -        "5  12   1 0,1,6,pop_vlan,4,7,push_vlan(vid=0,pcp=1),3,8" \
> -        "6  none 0 2,push_vlan(vid=10,pcp=0),0,1,5,7,8" \
> -        "6  0    0 pop_vlan,2,push_vlan(vid=10,pcp=0),0,1,5,7,8" \
> -        "6  0    1 pop_vlan,2,push_vlan(vid=10,pcp=1),0,1,5,7,8" \
> +        "5  12   1 0,1,6,pop_vlan,4,7,push_vlan(tpid=0x8100,vid=0,pcp=1),3,8" \
> +        "6  none 0 2,push_vlan(tpid=0x8100,vid=10,pcp=0),0,1,5,7,8" \
> +        "6  0    0 pop_vlan,2,push_vlan(tpid=0x8100,vid=10,pcp=0),0,1,5,7,8" \
> +        "6  0    1 pop_vlan,2,push_vlan(tpid=0x8100,vid=10,pcp=1),0,1,5,7,8" \
>          "6  10   0 0,1,5,7,8,pop_vlan,2" \
>          "6  10   1 0,1,5,7,8,pop_vlan,2" \
>          "6  11   0 drop" \
>          "6  11   1 drop" \
>          "6  12   0 0,1,5,pop_vlan,3,4,7,8" \
> -        "6  12   1 0,1,5,pop_vlan,4,7,push_vlan(vid=0,pcp=1),3,8" \
> -        "7  none 0 3,4,8,push_vlan(vid=12,pcp=0),0,1,5,6" \
> -        "7  0    0 pop_vlan,3,4,8,push_vlan(vid=12,pcp=0),0,1,5,6" \
> -        "7  0    1 3,8,pop_vlan,4,push_vlan(vid=12,pcp=1),0,1,5,6" \
> +        "6  12   1 0,1,5,pop_vlan,4,7,push_vlan(tpid=0x8100,vid=0,pcp=1),3,8" \
> +        "7  none 0 3,4,8,push_vlan(tpid=0x8100,vid=12,pcp=0),0,1,5,6" \
> +        "7  0    0 pop_vlan,3,4,8,push_vlan(tpid=0x8100,vid=12,pcp=0),0,1,5,6" \
> +        "7  0    1 3,8,pop_vlan,4,push_vlan(tpid=0x8100,vid=12,pcp=1),0,1,5,6" \
>          "7  10   0 0,1,5,6,8,pop_vlan,2" \
>          "7  10   1 0,1,5,6,8,pop_vlan,2" \
>          "7  11   0 0,5" \
>          "7  11   1 0,5" \
>          "7  12   0 0,1,5,6,pop_vlan,3,4,8" \
> -        "7  12   1 0,1,5,6,pop_vlan,4,push_vlan(vid=0,pcp=1),3,8" \
> -        "8  none 0 3,4,7,push_vlan(vid=12,pcp=0),0,1,5,6" \
> -        "8  0    0 pop_vlan,3,4,7,push_vlan(vid=12,pcp=0),0,1,5,6" \
> -        "8  0    1 3,pop_vlan,4,7,push_vlan(vid=12,pcp=1),0,1,5,6" \
> +        "7  12   1 0,1,5,6,pop_vlan,4,push_vlan(tpid=0x8100,vid=0,pcp=1),3,8" \
> +        "8  none 0 3,4,7,push_vlan(tpid=0x8100,vid=12,pcp=0),0,1,5,6" \
> +        "8  0    0 pop_vlan,3,4,7,push_vlan(tpid=0x8100,vid=12,pcp=0),0,1,5,6" \
> +        "8  0    1 3,pop_vlan,4,7,push_vlan(tpid=0x8100,vid=12,pcp=1),0,1,5,6" \
>          "8  10   0 0,1,5,6,7,pop_vlan,2" \
>          "8  10   1 0,1,5,6,7,pop_vlan,2" \
>          "8  11   0 drop" \
>          "8  11   1 drop" \
>          "8  12   0 0,1,5,6,pop_vlan,3,4,7" \
> -        "8  12   1 0,1,5,6,pop_vlan,4,7,push_vlan(vid=0,pcp=1),3"
> +        "8  12   1 0,1,5,6,pop_vlan,4,7,push_vlan(tpid=0x8100,vid=0,pcp=1),3"
>  do
>    set $tuple
>    in_port=$1
> @@ -860,7 +902,7 @@ AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
>  flow="in_port(1),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,ttl=128,frag=no),icmp(type=8,code=0)"
>  AT_CHECK([ovs-appctl ofproto/trace br0 "$flow"], [0], [stdout])
>  AT_CHECK_UNQUOTED([tail -1 stdout], [0],
> -  [Datapath actions: push_vlan(vid=17,pcp=0),2,pop_vlan,3
> +  [Datapath actions: push_vlan(tpid=0x8100,vid=17,pcp=0),2,pop_vlan,3
>  ])
>  
>  flow="in_port(2),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,ttl=128,frag=no),icmp(type=8,code=0)"
> @@ -891,7 +933,7 @@ flow="in_port(1),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x080
>  AT_CHECK([ovs-appctl ofproto/trace br0 "$flow"], [0], [stdout])
>  actual=`tail -1 stdout | sed 's/Datapath actions: //'`
>  
> -expected="2,push_vlan(vid=12,pcp=0),0,1,2"
> +expected="2,push_vlan(tpid=0x8100,vid=12,pcp=0),0,1,2"
>  AT_CHECK([ovs-dpctl normalize-actions "$flow" "$expected"], [0], [stdout])
>  mv stdout expout
>  AT_CHECK([ovs-dpctl normalize-actions "$flow" "$actual"], [0], [expout])
> @@ -900,7 +942,7 @@ flow="in_port(2),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x080
>  AT_CHECK([ovs-appctl ofproto/trace br0 "$flow"], [0], [stdout])
>  actual=`tail -1 stdout | sed 's/Datapath actions: //'`
>  
> -expected="push_vlan(vid=17,pcp=0),1,pop_vlan,push_vlan(vid=12,pcp=0),0,1,2"
> +expected="push_vlan(tpid=0x8100,vid=17,pcp=0),1,pop_vlan,push_vlan(tpid=0x8100,vid=12,pcp=0),0,1,2"
>  AT_CHECK([ovs-dpctl normalize-actions "$flow" "$expected"], [0], [stdout])
>  mv stdout expout
>  AT_CHECK([ovs-dpctl normalize-actions "$flow" "$actual"], [0], [expout])
> diff --git a/tests/ofproto.at b/tests/ofproto.at
> index d1ea8a0..63582d8 100644
> --- a/tests/ofproto.at
> +++ b/tests/ofproto.at
> @@ -580,21 +580,21 @@ check_async () {
>      ovs-ofctl -v packet-out br0 none controller '0001020304050010203040501234'
>      if test X"$1" = X"OFPR_ACTION"; then shift;
>          echo >>expout "OFPT_PACKET_IN: total_len=14 in_port=NONE (via action) data_len=14 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:1234,mpls(0) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)"
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:1234,mpls(0) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)"
>      fi
>  
>      # OFPT_PACKET_IN, OFPR_NO_MATCH (controller_id=123)
>      ovs-ofctl -v packet-out br0 none 'controller(reason=no_match,id=123)' '0001020304050010203040501234'
>      if test X"$1" = X"OFPR_NO_MATCH"; then shift;
>          echo >>expout "OFPT_PACKET_IN: total_len=14 in_port=NONE (via no_match) data_len=14 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:1234,mpls(0) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)"
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:1234,mpls(0) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)"
>      fi
>  
>      # OFPT_PACKET_IN, OFPR_INVALID_TTL (controller_id=0)
>      ovs-ofctl packet-out br0 none dec_ttl '002583dfb4000026b98cb0f908004500003fb7e200000011339bac11370dac100002d7730035002b8f6d86fb0100000100000000000006626c702d7873066e696369726103636f6d00000f00'
>      if test X"$1" = X"OFPR_INVALID_TTL"; then shift;
>          echo >>expout "OFPT_PACKET_IN: total_len=76 in_port=NONE (via invalid_ttl) data_len=76 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(00:26:b9:8c:b0:f9->00:25:83:df:b4:00) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(172.17.55.13->172.16.0.2) port(55155->53) udp_csum:8f6d"
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(00:26:b9:8c:b0:f9->00:25:83:df:b4:00) type:0800,mpls(0) proto:17 tos:0 ttl:0 ip(172.17.55.13->172.16.0.2) port(55155->53) udp_csum:8f6d"
>      fi
>  
>      # OFPT_PORT_STATUS, OFPPR_ADD
> @@ -692,9 +692,9 @@ ovs-appctl -t ovs-ofctl exit
>  
>  AT_CHECK([sed 's/ (xid=0x[[0-9a-fA-F]]*)//' monitor.log], [0], [dnl
>  OFPT_PACKET_IN: total_len=14 in_port=NONE (via action) data_len=14 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:1234,mpls(0) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:1234,mpls(0) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
>  OFPT_PACKET_IN: total_len=14 in_port=CONTROLLER (via action) data_len=14 (unbuffered)
> -priority:0,tunnel:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:5678,mpls(0) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
> +priority:0,tunnel:0,in_port:0000,tci(0),qinq_tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:5678,mpls(0) proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)
>  OFPT_BARRIER_REPLY:
>  ])
>  
> diff --git a/utilities/ovs-dpctl.c b/utilities/ovs-dpctl.c
> index 1322994..29eb45b 100644
> --- a/utilities/ovs-dpctl.c
> +++ b/utilities/ovs-dpctl.c
> @@ -886,11 +886,13 @@ do_normalize_actions(int argc, char *argv[])
>          switch(nl_attr_type(a)) {
>          case OVS_ACTION_ATTR_POP_VLAN:
>              flow.vlan_tci = htons(0);
> +            flow.vlan_tpid = htons(0);
>              continue;
>  
>          case OVS_ACTION_ATTR_PUSH_VLAN:
>              push = nl_attr_get_unspec(a, sizeof *push);
>              flow.vlan_tci = push->vlan_tci;
> +            flow.vlan_tpid = push->vlan_tpid;
>              continue;
>  
>          case OVS_ACTION_ATTR_PUSH_MPLS:
> diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
> index b3653e1..7624b29 100644
> --- a/utilities/ovs-ofctl.8.in
> +++ b/utilities/ovs-ofctl.8.in
> @@ -375,6 +375,19 @@ Matches IEEE 802.1q Priority Code Point (PCP) \fIpriority\fR, which is
>  specified as a value between 0 and 7, inclusive.  A higher value
>  indicates a higher frame priority level.
>  .
> +.IP \fBdl_vlan_tpid=\fIvlan_tpid\fR
> +Matches IEEE 802.1ad outer Virtual LAN tpid \fIvlan_tpid\fR. Specify either
> +0x88a8 or 0x8100 as a tpid to match.
> +.
> +.IP \fBdl_vlan_qinq_vid=\fIvlan\fR
> +Matches IEEE 802.1ad inner Virtual LAN tag \fIvlan\fR. Specify a number
> +between 0 and 4095, inclusive, as the 12-bit VLAN ID to match.
> +.
> +.IP \fBdl_vlan_qinq_pcp=\fIpriority\fR
> +Matches IEEE 802.1ad inner Priority Code Point (PCP) \fIpriority\fR, which is
> +specified as a value between 0 and 7, inclusive.  A higher value
> +indicates a higher frame priority level.
> +.
>  .IP \fBdl_src=\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fR
>  .IQ \fBdl_dst=\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fR
>  Matches an Ethernet source (or destination) address specified as 6
> @@ -850,6 +863,11 @@ as necessary to match the value specified.  Valid values are between 0
>  (lowest) and 7 (highest).  If the VLAN tag is added, a vid of zero is used 
>  (see the \fBmod_vlan_vid\fR action to set this).
>  .
> +.IP \fBpush_vlan\fR:\fIvlan_tpid\fR
> +Push new VLAN tag with the tpid specified. VLAN vid and priority of existing
> +vlan header is copied onto the new VLAN QinQ header. VLAN CFI for 8021Q or
> +VLAN DEI for 8021AD is ignored.
> +.
>  .IP \fBstrip_vlan\fR
>  Strips the VLAN tag from a packet if it is present.
>  .
> -- 
> 1.7.5.4
> 




More information about the dev mailing list