diff mbox

[ovs-dev,2/7] Add new port VLAN mode "dot1q-tunnel"

Message ID 20170301224805.619-3-e@erig.me
State Accepted
Headers show

Commit Message

Eric Garver March 1, 2017, 10:48 p.m. UTC
- Example:
     ovs-vsctl set Port p1 vlan_mode=dot1q-tunnel tag=100
   Pushes another VLAN 100 header on packets (tagged and untagged) on
   ingress, and pops it on egress.
 - Customer VLAN check:
     ovs-vsctl set Port p1 vlan_mode=dot1q-tunnel tag=100 cvlans=10,20
   Only customer VLAN of 10 and 20 are allowed.

Co-authored-by: Xiao Liang <shaw.leon@gmail.com>
Signed-off-by: Xiao Liang <shaw.leon@gmail.com>
Signed-off-by: Eric Garver <e@erig.me>
---
 NEWS                         |   1 +
 ofproto/ofproto-dpif-xlate.c |  76 ++++++++++++++++++++++++++----
 ofproto/ofproto-dpif-xlate.h |   4 +-
 ofproto/ofproto-dpif.c       |  33 ++++++++++++-
 ofproto/ofproto.h            |   8 +++-
 tests/ofproto-dpif.at        | 110 +++++++++++++++++++++++++------------------
 vswitchd/bridge.c            |  27 ++++++++++-
 vswitchd/vswitch.ovsschema   |  16 +++++--
 vswitchd/vswitch.xml         |  31 ++++++++++++
 9 files changed, 242 insertions(+), 64 deletions(-)

Comments

Eric Garver March 17, 2017, 3:51 p.m. UTC | #1
On Fri, Mar 17, 2017 at 08:39:01AM -0700, Ben Pfaff wrote:
> On Wed, Mar 01, 2017 at 05:48:00PM -0500, Eric Garver wrote:
> >  - Example:
> >      ovs-vsctl set Port p1 vlan_mode=dot1q-tunnel tag=100
> >    Pushes another VLAN 100 header on packets (tagged and untagged) on
> >    ingress, and pops it on egress.
> >  - Customer VLAN check:
> >      ovs-vsctl set Port p1 vlan_mode=dot1q-tunnel tag=100 cvlans=10,20
> >    Only customer VLAN of 10 and 20 are allowed.
> > 
> > Co-authored-by: Xiao Liang <shaw.leon@gmail.com>
> > Signed-off-by: Xiao Liang <shaw.leon@gmail.com>
> > Signed-off-by: Eric Garver <e@erig.me>
> 
> Thanks Eric and Xiao.
> 
> I applied this commit to master, folding in the following changes.  The
> important changes were:
> 
> * Clarified log message.
> 
> * Moved qinq_ethtype from a column of its own to other_config, because I
>   did not think that this was an important configuration setting.
> 
> * Rewrote and expanded the documentation, because I had never heard of
>   dot1-tunnel ports and thought that others deserved a detailed
>   explanation of what they do.
> 
> Eric, would you mind rebasing and resubmitting patches 3 through 7?  I
> imagine that I've mostly broken them with my changes, although I hope
> that the fixes are simple.

Not a problem. I'll get to it ASAP.


> --8<--------------------------cut here-------------------------->8--
> 
> diff --git a/NEWS b/NEWS
> index 2994b82b6a76..83a7c97c3cd9 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -3,7 +3,6 @@ Post-v2.7.0
>     - Tunnels:
>       * Added support to set packet mark for tunnel endpoint using
>         `egress_pkt_mark` OVSDB option.
> -     * New dot1q-tunnel (CVLAN) type via 802.1ad support.
>     - EMC insertion probability is reduced to 1% and is configurable via
>       the new 'other_config:emc-insert-inv-prob' option.
>     - DPDK:
> @@ -12,7 +11,8 @@ Post-v2.7.0
>         'ovs-appctl vlog' commands for 'dpdk' module. Lower bound
>         still can be configured via extra arguments for DPDK EAL.
>     - The "learn" action now supports a "limit" option (see ovs-ofctl(8)).
> -   - New support for multiple VLANs (802.1ad or "QinQ").
> +   - New support for multiple VLANs (802.1ad or "QinQ"), including a new
> +     "dot1q-tunnel" port VLAN mode.
>  
>  v2.7.0 - 21 Feb 2017
>  ---------------------
> diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
> index bc16e92847d7..1a82b8d569be 100644
> --- a/ofproto/ofproto-dpif-xlate.c
> +++ b/ofproto/ofproto-dpif-xlate.c
> @@ -1968,8 +1968,8 @@ input_vid_is_valid(const struct xlate_ctx *ctx,
>      case PORT_VLAN_DOT1Q_TUNNEL:
>          if (!xbundle_allows_cvlan(in_xbundle, vid)) {
>              xlate_report_error(ctx, "dropping VLAN %"PRIu16" packet received "
> -                               "on port %s not configured for dot1q tunneling"
> -                               "VLAN %"PRIu16, vid, in_xbundle->name, vid);
> +                               "on dot1q-tunnel port %s that excludes this "
> +                               "VLAN", vid, in_xbundle->name);
>              return false;
>          }
>          return true;
> diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
> index 8cc2fee234ff..6611c23a5903 100644
> --- a/ofproto/ofproto-dpif.c
> +++ b/ofproto/ofproto-dpif.c
> @@ -1,5 +1,5 @@
>  /*
> - * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Nicira, Inc.
> + * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 Nicira, Inc.
>   *
>   * Licensed under the Apache License, Version 2.0 (the "License");
>   * you may not use this file except in compliance with the License.
> @@ -2968,7 +2968,7 @@ bundle_set(struct ofproto *ofproto_, void *aux,
>      }
>  
>      if (s->qinq_ethtype != bundle->qinq_ethtype) {
> -        bundle->qinq_ethtype= s->qinq_ethtype;
> +        bundle->qinq_ethtype = s->qinq_ethtype;
>          need_flush = true;
>      }
>  
> @@ -3032,7 +3032,7 @@ bundle_set(struct ofproto *ofproto_, void *aux,
>  
>      if (!vlan_bitmap_equal(cvlans, bundle->cvlans)) {
>          free(bundle->cvlans);
> -        if (cvlans== s->cvlans) {
> +        if (cvlans == s->cvlans) {
>              bundle->cvlans = vlan_bitmap_clone(cvlans);
>          } else {
>              bundle->cvlans = cvlans;
> diff --git a/ofproto/ofproto.h b/ofproto/ofproto.h
> index d44845ce3cf6..1e48e1952aa2 100644
> --- a/ofproto/ofproto.h
> +++ b/ofproto/ofproto.h
> @@ -390,7 +390,7 @@ enum port_vlan_mode {
>       * Other VLANs in 'trunks' are trunked. */
>      PORT_VLAN_NATIVE_UNTAGGED,
>  
> -    /* 802.1q tunnel port. Incomming packets are added an outer vlan tag
> +    /* 802.1q tunnel port. Incoming packets are added an outer vlan tag
>       * 'vlan'. If 'cvlans' is set, only allows VLANs in 'cvlans'. */
>      PORT_VLAN_DOT1Q_TUNNEL
>  };
> diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
> index 0a0d36800449..e3d79bd0788d 100644
> --- a/tests/ofproto-dpif.at
> +++ b/tests/ofproto-dpif.at
> @@ -3182,10 +3182,10 @@ OVS_VSWITCHD_START(
>     add-port br0 p7 vlan_mode=native-untagged tag=12              -- \
>     add-port br0 p8 vlan_mode=native-untagged tag=12 trunks=10,12 \
>                     other-config:priority-tags=true               -- \
> -   add-port br0 p9 vlan_mode=dot1q-tunnel    tag=10              qinq_ethtype=802.1q  -- \
> -   add-port br0 p10 vlan_mode=dot1q-tunnel   tag=10 cvlans=10,12 qinq_ethtype=802.1q  -- \
> -   add-port br0 p11 vlan_mode=dot1q-tunnel   tag=12              qinq_ethtype=802.1q  -- \
> -   add-port br0 p12 vlan_mode=dot1q-tunnel   tag=12              qinq_ethtype=802.1q  \
> +   add-port br0 p9 vlan_mode=dot1q-tunnel    tag=10              other-config:qinq-ethtype=802.1q  -- \
> +   add-port br0 p10 vlan_mode=dot1q-tunnel   tag=10 cvlans=10,12 other-config:qinq-ethtype=802.1q  -- \
> +   add-port br0 p11 vlan_mode=dot1q-tunnel   tag=12              other-config:qinq-ethtype=802.1q  -- \
> +   add-port br0 p12 vlan_mode=dot1q-tunnel   tag=12              other-config:qinq-ethtype=802.1q  \
>                     other-config:priority-tags=true               -- \
>     set Interface p1 type=dummy -- \
>     set Interface p2 type=dummy -- \
> diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
> index 77fe686a51f2..b182e0a5a37c 100644
> --- a/vswitchd/bridge.c
> +++ b/vswitchd/bridge.c
> @@ -1,4 +1,4 @@
> -/* Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Nicira, Inc.
> +/* Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 Nicira, Inc.
>   *
>   * Licensed under the Apache License, Version 2.0 (the "License");
>   * you may not use this file except in compliance with the License.
> @@ -1013,22 +1013,10 @@ port_configure(struct port *port)
>          }
>      }
>  
> -    if (cfg->qinq_ethtype) {
> -        if (!strcmp(cfg->qinq_ethtype, "802.1q") ||
> -            !strcmp(cfg->qinq_ethtype, "0x8100")) {
> -            s.qinq_ethtype = ETH_TYPE_VLAN_8021Q;
> -        } else if (!strcmp(cfg->qinq_ethtype, "802.1ad") ||
> -                   !strcmp(cfg->qinq_ethtype, "0x88a8")) {
> -            s.qinq_ethtype = ETH_TYPE_VLAN_8021AD;
> -        } else {
> -            /* This "can't happen" because ovsdb-server should prevent it. */
> -            VLOG_WARN("port %s: invalid QinQ ethertype %s, falling "
> -                      "back to 802.1ad", port->name, cfg->qinq_ethtype);
> -            s.qinq_ethtype = ETH_TYPE_VLAN_8021AD;
> -        }
> -    } else {
> -        s.qinq_ethtype = ETH_TYPE_VLAN_8021AD;
> -    }
> +    const char *qe = smap_get_def(&cfg->other_config, "qinq-ethtype", "");
> +    s.qinq_ethtype = (!strcmp(qe, "802.1q")
> +                      ? ETH_TYPE_VLAN_8021Q
> +                      : ETH_TYPE_VLAN_8021AD);
>  
>      s.use_priority_tags = smap_get_bool(&cfg->other_config, "priority-tags",
>                                          false);
> diff --git a/vswitchd/vswitch.ovsschema b/vswitchd/vswitch.ovsschema
> index 97fad0621fe3..19b49daf118c 100644
> --- a/vswitchd/vswitch.ovsschema
> +++ b/vswitchd/vswitch.ovsschema
> @@ -1,6 +1,6 @@
>  {"name": "Open_vSwitch",
>   "version": "7.15.0",
> - "cksum": "2427506207 23400",
> + "cksum": "544856471 23228",
>   "tables": {
>     "Open_vSwitch": {
>       "columns": {
> @@ -160,10 +160,6 @@
>             "enum": ["set", ["trunk", "access", "native-tagged",
>                              "native-untagged", "dot1q-tunnel"]]},
>           "min": 0, "max": 1}},
> -       "qinq_ethtype": {
> -         "type": {"key": {"type": "string",
> -           "enum": ["set", ["802.1q", "802.1ad", "0x88a8", "0x8100"]]},
> -         "min": 0, "max": 1}},
>         "qos": {
>           "type": {"key": {"type": "uuid",
>                            "refTable": "QoS"},
> diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
> index 45cc6437f412..14297bf9ad98 100644
> --- a/vswitchd/vswitch.xml
> +++ b/vswitchd/vswitch.xml
> @@ -1281,7 +1281,39 @@
>      </column>
>  
>      <group title="VLAN Configuration">
> -      <p>Bridge ports support the following types of VLAN configuration:</p>
> +      <p>
> +        In short, a VLAN (short for ``virtual LAN'') is a way to partition a
> +        single switch into multiple switches.  VLANs can be confusing, so for
> +        an introduction, please refer to the question ``What's a VLAN?'' in the
> +        Open vSwitch FAQ.
> +      </p>
> +
> +      <p>
> +        A VLAN is sometimes encoded into a packet using a 802.1Q or 802.1ad
> +        VLAN header, but every packet is part of some VLAN whether or not it is
> +        encoded in the packet.  (A packet that appears to have no VLAN is part
> +        of VLAN 0, by default.)  As a result, it's useful to think of a VLAN as
> +        a metadata property of a packet, separate from how the VLAN is encoded.
> +        For a given port, this column determines how the encoding of a packet
> +        that ingresses or egresses the port maps to the packet's VLAN.  When a
> +        packet enters the switch, its VLAN is determined based on its setting
> +        in this column and its VLAN headers, if any, and then, conceptually,
> +        the VLAN headers are then stripped off.  Conversely, when a packet
> +        exits the switch, its VLAN and the settings in this column determine
> +        what VLAN headers, if any, are pushed onto the packet before it
> +        egresses the port.
> +      </p>
> +
> +      <p>
> +        The VLAN configuration in this column affects Open vSwitch only when it
> +        is doing ``normal switching.''  It does not affect flows set up by an
> +        OpenFlow controller, outside of the OpenFlow ``normal action.''
> +      </p>
> +
> +      <p>
> +        Bridge ports support the following types of VLAN configuration:
> +      </p>
> +
>        <dl>
>          <dt>trunk</dt>
>          <dd>
> @@ -1333,16 +1365,23 @@
>          <dt>dot1q-tunnel</dt>
>          <dd>
>            <p>
> -            A dot1q-tunnel port tunnels packets with VLAN specified in
> -            <ref column="tag"/> column. That is it adds 802.1Q header, with
> -            ethertype and VLAN ID specified in <ref column="qinq-ethtype"/>
> -            and <ref column="tag"/>, to both tagged and untagged packets on
> -            ingress, and pops dot1q header of this VLAN on egress.
> +            A dot1q-tunnel port is somewhat like an access port.  Like an
> +            access port, it carries packets on the single VLAN specified in the
> +            <ref column="tag"/> column and this VLAN, called the service VLAN,
> +            does not appear in an 802.1Q header for packets that ingress or
> +            egress on the port.  The main difference lies in the behavior when
> +            packets that include a 802.1Q header ingress on the port.  Whereas
> +            an access port drops such packets, a dot1q-tunnel port treats these
> +            as double-tagged with the outer service VLAN <ref column="tag"/>
> +            and the inner customer VLAN taken from the 802.1Q header.
> +            Correspondingly, to egress on the port, a packet outer VLAN (or
> +            only VLAN) must be <ref column="tag"/>, which is removed before
> +            egress, which exposes the inner (customer) VLAN if one is present.
>            </p>
> -
> +            
>            <p>
> -            If <ref column="cvlans"/> is set, only allows packets of these
> -            VLANs.
> +            If <ref column="cvlans"/> is set, only allows packets in the
> +            specified customer VLANs.
>            </p>
>          </dd>
>        </dl>
> @@ -1368,13 +1407,6 @@
>          </ul>
>        </column>
>  
> -      <column name="qinq_ethtype">
> -        <p>
> -          Ethertype of dot1q-tunnel port, could be either "802.1q"(0x8100) or
> -          "802.1ad"(0x88a8). Defaults to "802.1ad".
> -        </p>
> -      </column>
> -
>        <column name="tag">
>          <p>
>            For an access port, the port's implicitly tagged VLAN.  For a
> @@ -1398,9 +1430,32 @@
>  
>        <column name="cvlans">
>          <p>
> -          For a trunk-qinq port if specific cvlans are specified only those
> -          cvlans are 1ad tunneled, others are dropped. If no cvlans specified
> -          explicitly then all cvlans are 1ad tunneled.
> +          For a dot1q-tunnel port, the customer VLANs that this port includes.
> +          If this is empty, the port includes all customer VLANs.
> +        </p>
> +        <p>
> +          For other kinds of ports, this setting is ignored.
> +        </p>
> +      </column>
> +
> +      <column name="other_config" key="qinq-ethtype"
> +              type='{"type": "string", "enum": ["set", ["802.1ad", "802.1q"]]}'>
> +        <p>
> +          For a dot1q-tunnel port, this is the TPID for the service tag, that
> +          is, for the 802.1Q header that contains the service VLAN ID.  Because
> +          packets that actually ingress and egress a dot1q-tunnel port do not
> +          include an 802.1Q header for the service VLAN, this does not affect
> +          packets on the dot1q-tunnel port itself.  Rather, it determines the
> +          service VLAN for a packet that ingresses on a dot1q-tunnel port and
> +          egresses on a trunk port.
> +        </p>
> +        <p>
> +          The value <code>802.1ad</code> specifies TPID 0x88a8, which is also
> +          the default if the setting is omitted.  The value <code>802.1q</code>
> +          specifies TPID 0x8100.
> +        </p>
> +        <p>
> +          For other kinds of ports, this setting is ignored.
>          </p>
>        </column>
>  

This incremental looks good. Thanks for beefing up the documentation.

Eric.
diff mbox

Patch

diff --git a/NEWS b/NEWS
index ca95bd313198..bbd6c92dacc1 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,7 @@  Post-v2.7.0
    - Tunnels:
      * Added support to set packet mark for tunnel endpoint using
        `egress_pkt_mark` OVSDB option.
+     * New dot1q-tunnel (CVLAN) type via 802.1ad support.
    - EMC insertion probability is reduced to 1% and is configurable via
      the new 'other_config:emc-insert-inv-prob' option.
    - ovs-ofctl:
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 321b3ffc314a..53244f8f7685 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -127,9 +127,13 @@  struct xbundle {
     struct lacp *lacp;             /* LACP handle or null. */
 
     enum port_vlan_mode vlan_mode; /* VLAN mode. */
+    uint16_t qinq_ethtype;         /* Ethertype of dot1q-tunnel interface
+                                    * either 0x8100 or 0x88a8. */
     int vlan;                      /* -1=trunk port, else a 12-bit VLAN ID. */
     unsigned long *trunks;         /* Bitmap of trunked VLANs, if 'vlan' == -1.
                                     * NULL if all VLANs are trunked. */
+    unsigned long *cvlans;         /* Bitmap of allowed customer vlans,
+                                    * NULL if all VLANs are allowed */
     bool use_priority_tags;        /* Use 802.1p tag for frames in VLAN 0? */
     bool floodable;                /* No port has OFPUTIL_PC_NO_FLOOD set? */
     bool protected;                /* Protected port mode */
@@ -499,6 +503,7 @@  static bool input_vid_is_valid(const struct xlate_ctx *,
                                uint16_t vid, struct xbundle *);
 static void xvlan_copy(struct xvlan *dst, const struct xvlan *src);
 static void xvlan_pop(struct xvlan *src);
+static void xvlan_push_uninit(struct xvlan *src);
 static void xvlan_extract(const struct flow *, struct xvlan *);
 static void xvlan_put(struct flow *, const struct xvlan *);
 static void xvlan_input_translate(const struct xbundle *,
@@ -550,8 +555,8 @@  static void xlate_xbridge_set(struct xbridge *, struct dpif *,
                               const struct dpif_backer_support *);
 static void xlate_xbundle_set(struct xbundle *xbundle,
                               enum port_vlan_mode vlan_mode,
-                              int vlan,
-                              unsigned long *trunks,
+                              uint16_t qinq_ethtype, int vlan,
+                              unsigned long *trunks, unsigned long *cvlans,
                               bool use_priority_tags,
                               const struct bond *bond, const struct lacp *lacp,
                               bool floodable, bool protected);
@@ -857,8 +862,8 @@  xlate_xbridge_set(struct xbridge *xbridge,
 
 static void
 xlate_xbundle_set(struct xbundle *xbundle,
-                  enum port_vlan_mode vlan_mode,
-                  int vlan, unsigned long *trunks,
+                  enum port_vlan_mode vlan_mode, uint16_t qinq_ethtype,
+                  int vlan, unsigned long *trunks, unsigned long *cvlans,
                   bool use_priority_tags,
                   const struct bond *bond, const struct lacp *lacp,
                   bool floodable, bool protected)
@@ -866,8 +871,10 @@  xlate_xbundle_set(struct xbundle *xbundle,
     ovs_assert(xbundle->xbridge);
 
     xbundle->vlan_mode = vlan_mode;
+    xbundle->qinq_ethtype = qinq_ethtype;
     xbundle->vlan = vlan;
     xbundle->trunks = trunks;
+    xbundle->cvlans = cvlans;
     xbundle->use_priority_tags = use_priority_tags;
     xbundle->floodable = floodable;
     xbundle->protected = protected;
@@ -962,8 +969,8 @@  xlate_xbundle_copy(struct xbridge *xbridge, struct xbundle *xbundle)
     new_xbundle->name = xstrdup(xbundle->name);
     xlate_xbundle_init(new_xcfg, new_xbundle);
 
-    xlate_xbundle_set(new_xbundle, xbundle->vlan_mode,
-                      xbundle->vlan, xbundle->trunks,
+    xlate_xbundle_set(new_xbundle, xbundle->vlan_mode, xbundle->qinq_ethtype,
+                      xbundle->vlan, xbundle->trunks, xbundle->cvlans,
                       xbundle->use_priority_tags, xbundle->bond, xbundle->lacp,
                       xbundle->floodable, xbundle->protected);
     LIST_FOR_EACH (xport, bundle_node, &xbundle->xports) {
@@ -1157,8 +1164,8 @@  xlate_remove_ofproto(struct ofproto_dpif *ofproto)
 void
 xlate_bundle_set(struct ofproto_dpif *ofproto, struct ofbundle *ofbundle,
                  const char *name, enum port_vlan_mode vlan_mode,
-                 int vlan,
-                 unsigned long *trunks,
+                 uint16_t qinq_ethtype, int vlan,
+                 unsigned long *trunks, unsigned long *cvlans,
                  bool use_priority_tags,
                  const struct bond *bond, const struct lacp *lacp,
                  bool floodable, bool protected)
@@ -1179,7 +1186,7 @@  xlate_bundle_set(struct ofproto_dpif *ofproto, struct ofbundle *ofbundle,
     free(xbundle->name);
     xbundle->name = xstrdup(name);
 
-    xlate_xbundle_set(xbundle, vlan_mode, vlan, trunks,
+    xlate_xbundle_set(xbundle, vlan_mode, qinq_ethtype, vlan, trunks, cvlans,
                       use_priority_tags, bond, lacp, floodable, protected);
 }
 
@@ -1701,6 +1708,12 @@  xbundle_trunks_vlan(const struct xbundle *bundle, uint16_t vlan)
 }
 
 static bool
+xbundle_allows_cvlan(const struct xbundle *bundle, uint16_t vlan)
+{
+    return (!bundle->cvlans || bitmap_is_set(bundle->cvlans, vlan));
+}
+
+static bool
 xbundle_includes_vlan(const struct xbundle *xbundle, const struct xvlan *xvlan)
 {
     switch (xbundle->vlan_mode) {
@@ -1712,6 +1725,10 @@  xbundle_includes_vlan(const struct xbundle *xbundle, const struct xvlan *xvlan)
     case PORT_VLAN_NATIVE_TAGGED:
         return xbundle_trunks_vlan(xbundle, xvlan->v[0].vid);
 
+    case PORT_VLAN_DOT1Q_TUNNEL:
+        return xvlan->v[0].vid == xbundle->vlan &&
+               xbundle_allows_cvlan(xbundle, xvlan->v[1].vid);
+
     default:
         OVS_NOT_REACHED();
     }
@@ -1948,6 +1965,15 @@  input_vid_is_valid(const struct xlate_ctx *ctx,
         }
         return true;
 
+    case PORT_VLAN_DOT1Q_TUNNEL:
+        if (!xbundle_allows_cvlan(in_xbundle, vid)) {
+            xlate_report_error(ctx, "dropping VLAN %"PRIu16" packet received "
+                               "on port %s not configured for dot1q tunneling"
+                               "VLAN %"PRIu16, vid, in_xbundle->name, vid);
+            return false;
+        }
+        return true;
+
     default:
         OVS_NOT_REACHED();
     }
@@ -1968,6 +1994,13 @@  xvlan_pop(struct xvlan *src)
            sizeof(src->v[FLOW_MAX_VLAN_HEADERS - 1]));
 }
 
+static void
+xvlan_push_uninit(struct xvlan *src)
+{
+    memmove(&src->v[1], &src->v[0], sizeof(src->v) - sizeof(src->v[0]));
+    memset(&src->v[0], 0, sizeof(src->v[0]));
+}
+
 /* Extract VLAN information (headers) from flow */
 static void
 xvlan_extract(const struct flow *flow, struct xvlan *xvlan)
@@ -2035,6 +2068,14 @@  xvlan_input_translate(const struct xbundle *in_xbundle,
         }
         break;
 
+    case PORT_VLAN_DOT1Q_TUNNEL:
+        xvlan_copy(xvlan, in_xvlan);
+        xvlan_push_uninit(xvlan);
+        xvlan->v[0].tpid = in_xbundle->qinq_ethtype;
+        xvlan->v[0].vid = in_xbundle->vlan;
+        xvlan->v[0].pcp = 0;
+        break;
+
     default:
         OVS_NOT_REACHED();
     }
@@ -2064,11 +2105,26 @@  xvlan_output_translate(const struct xbundle *out_xbundle,
         }
         break;
 
+    case PORT_VLAN_DOT1Q_TUNNEL:
+        xvlan_copy(out_xvlan, xvlan);
+        xvlan_pop(out_xvlan);
+        break;
+
     default:
         OVS_NOT_REACHED();
     }
 }
 
+/* If output xbundle is dot1q-tunnel, set mask bits of cvlan */
+static void
+check_and_set_cvlan_mask(struct flow_wildcards *wc,
+                         const struct xbundle *xbundle)
+{
+    if (xbundle->vlan_mode == PORT_VLAN_DOT1Q_TUNNEL && xbundle->cvlans) {
+        wc->masks.vlans[1].tci = htons(0xffff);
+    }
+}
+
 static void
 output_normal(struct xlate_ctx *ctx, const struct xbundle *out_xbundle,
               const struct xvlan *xvlan)
@@ -2080,6 +2136,8 @@  output_normal(struct xlate_ctx *ctx, const struct xbundle *out_xbundle,
     bool use_recirc = false;
     struct xvlan out_xvlan;
 
+    check_and_set_cvlan_mask(ctx->wc, out_xbundle);
+
     xvlan_output_translate(out_xbundle, xvlan, &out_xvlan);
     if (out_xbundle->use_priority_tags) {
         out_xvlan.v[0].pcp = ntohs(ctx->xin->flow.vlans[0].tci) &
diff --git a/ofproto/ofproto-dpif-xlate.h b/ofproto/ofproto-dpif-xlate.h
index 708a19af0799..8a6bdf631600 100644
--- a/ofproto/ofproto-dpif-xlate.h
+++ b/ofproto/ofproto-dpif-xlate.h
@@ -150,8 +150,8 @@  void xlate_remove_ofproto(struct ofproto_dpif *);
 
 void xlate_bundle_set(struct ofproto_dpif *, struct ofbundle *,
                       const char *name, enum port_vlan_mode,
-                      int vlan,
-                      unsigned long *trunks,
+                      uint16_t qinq_ethtype, int vlan,
+                      unsigned long *trunks, unsigned long *cvlans,
                       bool use_priority_tags,
                       const struct bond *, const struct lacp *,
                       bool floodable, bool protected);
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 7f1d60493b99..aaea15386e79 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -94,9 +94,11 @@  struct ofbundle {
     /* Configuration. */
     struct ovs_list ports;      /* Contains "struct ofport"s. */
     enum port_vlan_mode vlan_mode; /* VLAN mode */
+    uint16_t qinq_ethtype;
     int vlan;                   /* -1=trunk port, else a 12-bit VLAN ID. */
     unsigned long *trunks;      /* Bitmap of trunked VLANs, if 'vlan' == -1.
                                  * NULL if all VLANs are trunked. */
+    unsigned long *cvlans;
     struct lacp *lacp;          /* LACP if LACP is enabled, otherwise NULL. */
     struct bond *bond;          /* Nonnull iff more than one port. */
     bool use_priority_tags;     /* Use 802.1p tag for frames in VLAN 0? */
@@ -449,8 +451,8 @@  type_run(const char *type)
 
             HMAP_FOR_EACH (bundle, hmap_node, &ofproto->bundles) {
                 xlate_bundle_set(ofproto, bundle, bundle->name,
-                                 bundle->vlan_mode,
-                                 bundle->vlan, bundle->trunks,
+                                 bundle->vlan_mode, bundle->qinq_ethtype,
+                                 bundle->vlan, bundle->trunks, bundle->cvlans,
                                  bundle->use_priority_tags,
                                  bundle->bond, bundle->lacp,
                                  bundle->floodable, bundle->protected);
@@ -2796,6 +2798,7 @@  bundle_destroy(struct ofbundle *bundle)
     hmap_remove(&ofproto->bundles, &bundle->hmap_node);
     free(bundle->name);
     free(bundle->trunks);
+    free(bundle->cvlans);
     lacp_unref(bundle->lacp);
     bond_unref(bundle->bond);
     free(bundle);
@@ -2810,6 +2813,7 @@  bundle_set(struct ofproto *ofproto_, void *aux,
     struct ofport_dpif *port;
     struct ofbundle *bundle;
     unsigned long *trunks = NULL;
+    unsigned long *cvlans = NULL;
     int vlan;
     size_t i;
     bool ok;
@@ -2834,8 +2838,10 @@  bundle_set(struct ofproto *ofproto_, void *aux,
 
         ovs_list_init(&bundle->ports);
         bundle->vlan_mode = PORT_VLAN_TRUNK;
+        bundle->qinq_ethtype = ETH_TYPE_VLAN_8021AD;
         bundle->vlan = -1;
         bundle->trunks = NULL;
+        bundle->cvlans = NULL;
         bundle->use_priority_tags = s->use_priority_tags;
         bundle->lacp = NULL;
         bundle->bond = NULL;
@@ -2900,6 +2906,11 @@  bundle_set(struct ofproto *ofproto_, void *aux,
         need_flush = true;
     }
 
+    if (s->qinq_ethtype != bundle->qinq_ethtype) {
+        bundle->qinq_ethtype= s->qinq_ethtype;
+        need_flush = true;
+    }
+
     /* Set VLAN tag. */
     vlan = (s->vlan_mode == PORT_VLAN_TRUNK ? -1
             : s->vlan >= 0 && s->vlan <= 4095 ? s->vlan
@@ -2937,6 +2948,10 @@  bundle_set(struct ofproto *ofproto_, void *aux,
         }
         break;
 
+    case PORT_VLAN_DOT1Q_TUNNEL:
+        cvlans = CONST_CAST(unsigned long *, s->cvlans);
+        break;
+
     default:
         OVS_NOT_REACHED();
     }
@@ -2954,6 +2969,20 @@  bundle_set(struct ofproto *ofproto_, void *aux,
         free(trunks);
     }
 
+    if (!vlan_bitmap_equal(cvlans, bundle->cvlans)) {
+        free(bundle->cvlans);
+        if (cvlans== s->cvlans) {
+            bundle->cvlans = vlan_bitmap_clone(cvlans);
+        } else {
+            bundle->cvlans = cvlans;
+            cvlans = NULL;
+        }
+        need_flush = true;
+    }
+    if (cvlans != s->cvlans) {
+        free(cvlans);
+    }
+
     /* Bonding. */
     if (!ovs_list_is_short(&bundle->ports)) {
         bundle->ofproto->has_bonded_bundles = true;
diff --git a/ofproto/ofproto.h b/ofproto/ofproto.h
index 2b36eaf410a5..d44845ce3cf6 100644
--- a/ofproto/ofproto.h
+++ b/ofproto/ofproto.h
@@ -388,7 +388,11 @@  enum port_vlan_mode {
     /* Untagged incoming packets are part of 'vlan', as are incoming packets
      * tagged with 'vlan'.  Outgoing packets tagged with 'vlan' are untagged.
      * Other VLANs in 'trunks' are trunked. */
-    PORT_VLAN_NATIVE_UNTAGGED
+    PORT_VLAN_NATIVE_UNTAGGED,
+
+    /* 802.1q tunnel port. Incomming packets are added an outer vlan tag
+     * 'vlan'. If 'cvlans' is set, only allows VLANs in 'cvlans'. */
+    PORT_VLAN_DOT1Q_TUNNEL
 };
 
 /* Configuration of bundles. */
@@ -399,8 +403,10 @@  struct ofproto_bundle_settings {
     size_t n_slaves;
 
     enum port_vlan_mode vlan_mode; /* Selects mode for vlan and trunks */
+    uint16_t qinq_ethtype;
     int vlan;                   /* VLAN VID, except for PORT_VLAN_TRUNK. */
     unsigned long *trunks;      /* vlan_bitmap, except for PORT_VLAN_ACCESS. */
+    unsigned long *cvlans;
     bool use_priority_tags;     /* Use 802.1p tag for frames in VLAN 0? */
 
     struct bond_settings *bond; /* Must be nonnull iff if n_slaves > 1. */
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 56adf8c9e992..af90a7ce2056 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -3182,6 +3182,11 @@  OVS_VSWITCHD_START(
    add-port br0 p7 vlan_mode=native-untagged tag=12              -- \
    add-port br0 p8 vlan_mode=native-untagged tag=12 trunks=10,12 \
                    other-config:priority-tags=true               -- \
+   add-port br0 p9 vlan_mode=dot1q-tunnel    tag=10              qinq_ethtype=802.1q  -- \
+   add-port br0 p10 vlan_mode=dot1q-tunnel   tag=10 cvlans=10,12 qinq_ethtype=802.1q  -- \
+   add-port br0 p11 vlan_mode=dot1q-tunnel   tag=12              qinq_ethtype=802.1q  -- \
+   add-port br0 p12 vlan_mode=dot1q-tunnel   tag=12              qinq_ethtype=802.1q  \
+                   other-config:priority-tags=true               -- \
    set Interface p1 type=dummy -- \
    set Interface p2 type=dummy -- \
    set Interface p3 type=dummy -- \
@@ -3189,7 +3194,11 @@  OVS_VSWITCHD_START(
    set Interface p5 type=dummy -- \
    set Interface p6 type=dummy -- \
    set Interface p7 type=dummy -- \
-   set Interface p8 type=dummy --])
+   set Interface p8 type=dummy -- \
+   set Interface p9 type=dummy -- \
+   set Interface p10 type=dummy -- \
+   set Interface p11 type=dummy -- \
+   set Interface p12 type=dummy --])
 
 dnl Each of these specifies an in_port by number, a VLAN VID (or "none"),
 dnl a VLAN PCP (used if the VID isn't "none") and the expected set of datapath
@@ -3198,84 +3207,93 @@  for tuple in \
         "100 none 0 drop" \
         "100 0    0 drop" \
         "100 0    1 drop" \
-        "100 10   0 1,5,6,7,8,pop_vlan,2" \
-        "100 10   1 1,5,6,7,8,pop_vlan,2" \
+        "100 10   0 1,5,6,7,8,pop_vlan,2,9" \
+        "100 10   1 1,5,6,7,8,pop_vlan,2,9" \
         "100 11   0 5,7" \
         "100 11   1 5,7" \
-        "100 12   0 1,5,6,pop_vlan,3,4,7,8" \
-        "100 12   1 1,5,6,pop_vlan,4,7,push_vlan(vid=0,pcp=1),3,8" \
+        "100 12   0 1,5,6,pop_vlan,3,4,7,8,11,12" \
+        "100 12   1 1,5,6,pop_vlan,4,7,11,push_vlan(vid=0,pcp=1),3,8,12" \
         "1  none 0 drop" \
         "1  0    0 drop" \
         "1  0    1 drop" \
-        "1  10   0 5,6,7,8,100,pop_vlan,2" \
-        "1  10   1 5,6,7,8,100,pop_vlan,2" \
+        "1  10   0 5,6,7,8,100,pop_vlan,2,9" \
+        "1  10   1 5,6,7,8,100,pop_vlan,2,9" \
         "1  11   0 drop" \
         "1  11   1 drop" \
-        "1  12   0 5,6,100,pop_vlan,3,4,7,8" \
-        "1  12   1 5,6,100,pop_vlan,4,7,push_vlan(vid=0,pcp=1),3,8" \
-        "2  none 0 push_vlan(vid=10,pcp=0),1,5,6,7,8,100" \
-        "2  0    0 pop_vlan,push_vlan(vid=10,pcp=0),1,5,6,7,8,100" \
-        "2  0    1 pop_vlan,push_vlan(vid=10,pcp=1),1,5,6,7,8,100" \
+        "1  12   0 5,6,100,pop_vlan,3,4,7,8,11,12" \
+        "1  12   1 5,6,100,pop_vlan,4,7,11,push_vlan(vid=0,pcp=1),3,8,12" \
+        "2  none 0 9,push_vlan(vid=10,pcp=0),1,5,6,7,8,100" \
+        "2  0    0 pop_vlan,9,push_vlan(vid=10,pcp=0),1,5,6,7,8,100" \
+        "2  0    1 pop_vlan,9,push_vlan(vid=10,pcp=1),1,5,6,7,8,100" \
         "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),1,5,6,100" \
-        "3  0    0 pop_vlan,4,7,8,push_vlan(vid=12,pcp=0),1,5,6,100" \
-        "3  0    1 8,pop_vlan,4,7,push_vlan(vid=12,pcp=1),1,5,6,100" \
+        "3  none 0 4,7,8,11,12,push_vlan(vid=12,pcp=0),1,5,6,100" \
+        "3  0    0 pop_vlan,4,7,8,11,12,push_vlan(vid=12,pcp=0),1,5,6,100" \
+        "3  0    1 8,12,pop_vlan,4,7,11,push_vlan(vid=12,pcp=1),1,5,6,100" \
         "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),1,5,6,100" \
-        "4  0    0 pop_vlan,3,7,8,push_vlan(vid=12,pcp=0),1,5,6,100" \
-        "4  0    1 3,8,pop_vlan,7,push_vlan(vid=12,pcp=1),1,5,6,100" \
+        "4  none 0 3,7,8,11,12,push_vlan(vid=12,pcp=0),1,5,6,100" \
+        "4  0    0 pop_vlan,3,7,8,11,12,push_vlan(vid=12,pcp=0),1,5,6,100" \
+        "4  0    1 3,8,12,pop_vlan,7,11,push_vlan(vid=12,pcp=1),1,5,6,100" \
         "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),1,6,7,8,100" \
-        "5  0    0 pop_vlan,2,push_vlan(vid=10,pcp=0),1,6,7,8,100" \
-        "5  0    1 pop_vlan,2,push_vlan(vid=10,pcp=1),1,6,7,8,100" \
-        "5  10   0 1,6,7,8,100,pop_vlan,2" \
-        "5  10   1 1,6,7,8,100,pop_vlan,2" \
+        "5  none 0 2,9,push_vlan(vid=10,pcp=0),1,6,7,8,100" \
+        "5  0    0 pop_vlan,2,9,push_vlan(vid=10,pcp=0),1,6,7,8,100" \
+        "5  0    1 pop_vlan,2,9,push_vlan(vid=10,pcp=1),1,6,7,8,100" \
+        "5  10   0 1,6,7,8,100,pop_vlan,2,9" \
+        "5  10   1 1,6,7,8,100,pop_vlan,2,9" \
         "5  11   0 7,100" \
         "5  11   1 7,100" \
-        "5  12   0 1,6,100,pop_vlan,3,4,7,8" \
-        "5  12   1 1,6,100,pop_vlan,4,7,push_vlan(vid=0,pcp=1),3,8" \
-        "6  none 0 2,push_vlan(vid=10,pcp=0),1,5,7,8,100" \
-        "6  0    0 pop_vlan,2,push_vlan(vid=10,pcp=0),1,5,7,8,100" \
-        "6  0    1 pop_vlan,2,push_vlan(vid=10,pcp=1),1,5,7,8,100" \
-        "6  10   0 1,5,7,8,100,pop_vlan,2" \
-        "6  10   1 1,5,7,8,100,pop_vlan,2" \
+        "5  12   0 1,6,100,pop_vlan,3,4,7,8,11,12" \
+        "5  12   1 1,6,100,pop_vlan,4,7,11,push_vlan(vid=0,pcp=1),3,8,12" \
+        "6  none 0 2,9,push_vlan(vid=10,pcp=0),1,5,7,8,100" \
+        "6  0    0 pop_vlan,2,9,push_vlan(vid=10,pcp=0),1,5,7,8,100" \
+        "6  0    1 pop_vlan,2,9,push_vlan(vid=10,pcp=1),1,5,7,8,100" \
+        "6  10   0 1,5,7,8,100,pop_vlan,2,9" \
+        "6  10   1 1,5,7,8,100,pop_vlan,2,9" \
         "6  11   0 drop" \
         "6  11   1 drop" \
-        "6  12   0 1,5,100,pop_vlan,3,4,7,8" \
-        "6  12   1 1,5,100,pop_vlan,4,7,push_vlan(vid=0,pcp=1),3,8" \
-        "7  none 0 3,4,8,push_vlan(vid=12,pcp=0),1,5,6,100" \
-        "7  0    0 pop_vlan,3,4,8,push_vlan(vid=12,pcp=0),1,5,6,100" \
-        "7  0    1 3,8,pop_vlan,4,push_vlan(vid=12,pcp=1),1,5,6,100" \
-        "7  10   0 1,5,6,8,100,pop_vlan,2" \
-        "7  10   1 1,5,6,8,100,pop_vlan,2" \
+        "6  12   0 1,5,100,pop_vlan,3,4,7,8,11,12" \
+        "6  12   1 1,5,100,pop_vlan,4,7,11,push_vlan(vid=0,pcp=1),3,8,12" \
+        "7  none 0 3,4,8,11,12,push_vlan(vid=12,pcp=0),1,5,6,100" \
+        "7  0    0 pop_vlan,3,4,8,11,12,push_vlan(vid=12,pcp=0),1,5,6,100" \
+        "7  0    1 3,8,12,pop_vlan,4,11,push_vlan(vid=12,pcp=1),1,5,6,100" \
+        "7  10   0 1,5,6,8,100,pop_vlan,2,9" \
+        "7  10   1 1,5,6,8,100,pop_vlan,2,9" \
         "7  11   0 5,100" \
         "7  11   1 5,100" \
-        "7  12   0 1,5,6,100,pop_vlan,3,4,8" \
-        "7  12   1 1,5,6,100,pop_vlan,4,push_vlan(vid=0,pcp=1),3,8" \
-        "8  none 0 3,4,7,push_vlan(vid=12,pcp=0),1,5,6,100" \
-        "8  0    0 pop_vlan,3,4,7,push_vlan(vid=12,pcp=0),1,5,6,100" \
-        "8  0    1 3,pop_vlan,4,7,push_vlan(vid=12,pcp=1),1,5,6,100" \
-        "8  10   0 1,5,6,7,100,pop_vlan,2" \
-        "8  10   1 1,5,6,7,100,pop_vlan,2" \
+        "7  12   0 1,5,6,100,pop_vlan,3,4,8,11,12" \
+        "7  12   1 1,5,6,100,pop_vlan,4,11,push_vlan(vid=0,pcp=1),3,8,12" \
+        "8  none 0 3,4,7,11,12,push_vlan(vid=12,pcp=0),1,5,6,100" \
+        "8  0    0 pop_vlan,3,4,7,11,12,push_vlan(vid=12,pcp=0),1,5,6,100" \
+        "8  0    1 3,12,pop_vlan,4,7,11,push_vlan(vid=12,pcp=1),1,5,6,100" \
+        "8  10   0 1,5,6,7,100,pop_vlan,2,9" \
+        "8  10   1 1,5,6,7,100,pop_vlan,2,9" \
         "8  11   0 drop" \
         "8  11   1 drop" \
-        "8  12   0 1,5,6,100,pop_vlan,3,4,7" \
-        "8  12   1 1,5,6,100,pop_vlan,4,7,push_vlan(vid=0,pcp=1),3"
+        "8  12   0 1,5,6,100,pop_vlan,3,4,7,11,12" \
+        "8  12   1 1,5,6,100,pop_vlan,4,7,11,push_vlan(vid=0,pcp=1),3,12" \
+        "9  none 0 2,push_vlan(vid=10,pcp=0),1,5,6,7,8,100" \
+        "9  10   0 10,push_vlan(vid=10,pcp=0),1,5,6,7,8,100" \
+        "9  11   0 push_vlan(vid=10,pcp=0),1,5,6,7,8,100" \
+        "10 none 0 drop" \
+        "10 0    0 drop" \
+        "10 11   0 drop" \
+        "10 12   0 9,push_vlan(vid=10,pcp=0),1,5,6,7,8,100" \
+        "11 10   0 7,8,12,push_vlan(vid=12,pcp=0),1,5,6,100" \
+        "11 10   1 7,8,12,push_vlan(vid=12,pcp=0),1,5,6,100"
 do
   set $tuple
   in_port=$1
diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
index f1483112b46f..77fe686a51f2 100644
--- a/vswitchd/bridge.c
+++ b/vswitchd/bridge.c
@@ -978,6 +978,11 @@  port_configure(struct port *port)
         s.trunks = vlan_bitmap_from_array(cfg->trunks, cfg->n_trunks);
     }
 
+    s.cvlans = NULL;
+    if (cfg->n_cvlans) {
+        s.cvlans = vlan_bitmap_from_array(cfg->cvlans, cfg->n_cvlans);
+    }
+
     /* Get VLAN mode. */
     if (cfg->vlan_mode) {
         if (!strcmp(cfg->vlan_mode, "access")) {
@@ -988,6 +993,8 @@  port_configure(struct port *port)
             s.vlan_mode = PORT_VLAN_NATIVE_TAGGED;
         } else if (!strcmp(cfg->vlan_mode, "native-untagged")) {
             s.vlan_mode = PORT_VLAN_NATIVE_UNTAGGED;
+        } else if (!strcmp(cfg->vlan_mode, "dot1q-tunnel")) {
+            s.vlan_mode = PORT_VLAN_DOT1Q_TUNNEL;
         } else {
             /* This "can't happen" because ovsdb-server should prevent it. */
             VLOG_WARN("port %s: unknown VLAN mode %s, falling "
@@ -997,7 +1004,7 @@  port_configure(struct port *port)
     } else {
         if (s.vlan >= 0) {
             s.vlan_mode = PORT_VLAN_ACCESS;
-            if (cfg->n_trunks) {
+            if (cfg->n_trunks || cfg->n_cvlans) {
                 VLOG_WARN("port %s: ignoring trunks in favor of implicit vlan",
                           port->name);
             }
@@ -1005,6 +1012,24 @@  port_configure(struct port *port)
             s.vlan_mode = PORT_VLAN_TRUNK;
         }
     }
+
+    if (cfg->qinq_ethtype) {
+        if (!strcmp(cfg->qinq_ethtype, "802.1q") ||
+            !strcmp(cfg->qinq_ethtype, "0x8100")) {
+            s.qinq_ethtype = ETH_TYPE_VLAN_8021Q;
+        } else if (!strcmp(cfg->qinq_ethtype, "802.1ad") ||
+                   !strcmp(cfg->qinq_ethtype, "0x88a8")) {
+            s.qinq_ethtype = ETH_TYPE_VLAN_8021AD;
+        } else {
+            /* This "can't happen" because ovsdb-server should prevent it. */
+            VLOG_WARN("port %s: invalid QinQ ethertype %s, falling "
+                      "back to 802.1ad", port->name, cfg->qinq_ethtype);
+            s.qinq_ethtype = ETH_TYPE_VLAN_8021AD;
+        }
+    } else {
+        s.qinq_ethtype = ETH_TYPE_VLAN_8021AD;
+    }
+
     s.use_priority_tags = smap_get_bool(&cfg->other_config, "priority-tags",
                                         false);
 
diff --git a/vswitchd/vswitch.ovsschema b/vswitchd/vswitch.ovsschema
index de78dfd26d2c..c1804c847e1d 100644
--- a/vswitchd/vswitch.ovsschema
+++ b/vswitchd/vswitch.ovsschema
@@ -1,6 +1,6 @@ 
 {"name": "Open_vSwitch",
- "version": "7.15.0",
- "cksum": "2264024465 22987",
+ "version": "7.16.0",
+ "cksum": "125603819 23400",
  "tables": {
    "Open_vSwitch": {
      "columns": {
@@ -145,6 +145,11 @@ 
                           "minInteger": 0,
                           "maxInteger": 4095},
                   "min": 0, "max": 4096}},
+       "cvlans": {
+         "type": {"key": {"type": "integer",
+                          "minInteger": 0,
+                          "maxInteger": 4095},
+                  "min": 0, "max": 4096}},
        "tag": {
          "type": {"key": {"type": "integer",
                           "minInteger": 0,
@@ -152,7 +157,12 @@ 
                   "min": 0, "max": 1}},
        "vlan_mode": {
          "type": {"key": {"type": "string",
-           "enum": ["set", ["trunk", "access", "native-tagged", "native-untagged"]]},
+           "enum": ["set", ["trunk", "access", "native-tagged",
+                            "native-untagged", "dot1q-tunnel"]]},
+         "min": 0, "max": 1}},
+       "qinq_ethtype": {
+         "type": {"key": {"type": "string",
+           "enum": ["set", ["802.1q", "802.1ad", "0x88a8", "0x8100"]]},
          "min": 0, "max": 1}},
        "qos": {
          "type": {"key": {"type": "uuid",
diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
index 14f5d14511fa..79b5462eb583 100644
--- a/vswitchd/vswitch.xml
+++ b/vswitchd/vswitch.xml
@@ -1326,6 +1326,22 @@ 
           exception that a packet that egresses on a native-untagged port in
           the native VLAN will not have an 802.1Q header.
         </dd>
+
+        <dt>dot1q-tunnel</dt>
+        <dd>
+          <p>
+            A dot1q-tunnel port tunnels packets with VLAN specified in
+            <ref column="tag"/> column. That is it adds 802.1Q header, with
+            ethertype and VLAN ID specified in <ref column="qinq-ethtype"/>
+            and <ref column="tag"/>, to both tagged and untagged packets on
+            ingress, and pops dot1q header of this VLAN on egress.
+          </p>
+
+          <p>
+            If <ref column="cvlans"/> is set, only allows packets of these
+            VLANs.
+          </p>
+        </dd>
       </dl>
       <p>
         A packet will only egress through bridge ports that carry the VLAN of
@@ -1349,6 +1365,13 @@ 
         </ul>
       </column>
 
+      <column name="qinq_ethtype">
+        <p>
+          Ethertype of dot1q-tunnel port, could be either "802.1q"(0x8100) or
+          "802.1ad"(0x88a8). Defaults to "802.1ad".
+        </p>
+      </column>
+
       <column name="tag">
         <p>
           For an access port, the port's implicitly tagged VLAN.  For a
@@ -1370,6 +1393,14 @@ 
         </p>
       </column>
 
+      <column name="cvlans">
+        <p>
+          For a trunk-qinq port if specific cvlans are specified only those
+          cvlans are 1ad tunneled, others are dropped. If no cvlans specified
+          explicitly then all cvlans are 1ad tunneled.
+        </p>
+      </column>
+
       <column name="other_config" key="priority-tags"
               type='{"type": "boolean"}'>
         <p>