diff mbox series

[ovs-dev,v3] Support 802.11ad EthType for localnet ports

Message ID 20210416170708.3970380-1-ihrachys@redhat.com
State Accepted
Headers show
Series [ovs-dev,v3] Support 802.11ad EthType for localnet ports | expand

Commit Message

Ihar Hrachyshka April 16, 2021, 5:07 p.m. UTC
In some environments, hardware serving the fabric network doesn't
support double 802.1q (0x8100) VLAN tags, but does support 802.11ad
(0x8a88) EthType for two layer VLAN traffic. Specifically, Cisco
hardware UCS VIC was identified affected by this limitation.

With vlan-passthru=true set for a logical switch, VLAN tagged traffic
may be generated by VIFs. This patch allows to support the feature in
affected hardware environments.

Signed-off-by: Ihar Hrachyshka <ihrachys@redhat.com>

---

v1: initial version.
v2: fixed test scenario to actually validate packets sent by vifs.
v2: stylistic (spacing) change for a ternary operator.
v3: ux: switch to symbolic vlan ethtype representation.
---
 NEWS                  |  1 +
 controller/physical.c | 43 ++++++++++++++++++----
 ovn-nb.xml            |  6 +++
 tests/ovn.at          | 85 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 127 insertions(+), 8 deletions(-)

Comments

Mark Michelson April 19, 2021, 12:21 p.m. UTC | #1
Acked-by: Mark Michelson <mmichels@redhat.com>

On 4/16/21 1:07 PM, Ihar Hrachyshka wrote:
> In some environments, hardware serving the fabric network doesn't
> support double 802.1q (0x8100) VLAN tags, but does support 802.11ad
> (0x8a88) EthType for two layer VLAN traffic. Specifically, Cisco
> hardware UCS VIC was identified affected by this limitation.
> 
> With vlan-passthru=true set for a logical switch, VLAN tagged traffic
> may be generated by VIFs. This patch allows to support the feature in
> affected hardware environments.
> 
> Signed-off-by: Ihar Hrachyshka <ihrachys@redhat.com>
> 
> ---
> 
> v1: initial version.
> v2: fixed test scenario to actually validate packets sent by vifs.
> v2: stylistic (spacing) change for a ternary operator.
> v3: ux: switch to symbolic vlan ethtype representation.
> ---
>   NEWS                  |  1 +
>   controller/physical.c | 43 ++++++++++++++++++----
>   ovn-nb.xml            |  6 +++
>   tests/ovn.at          | 85 +++++++++++++++++++++++++++++++++++++++++++
>   4 files changed, 127 insertions(+), 8 deletions(-)
> 
> diff --git a/NEWS b/NEWS
> index 0e98d5e36..1ddde15f8 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -10,6 +10,7 @@ Post-v21.03.0
>     - Introduced parallel processing in ovn-northd with the NB_Global config option
>       'use_parallel_build' to enable it.  It is disabled by default.
>     - Support vlan-passthru mode for tag=0 localnet ports.
> +  - Support custom 802.11ad EthType for localnet ports.
>   
>   OVN v21.03.0 - 12 Mar 2021
>   -------------------------
> diff --git a/controller/physical.c b/controller/physical.c
> index 3710bf407..6ed02df29 100644
> --- a/controller/physical.c
> +++ b/controller/physical.c
> @@ -609,6 +609,37 @@ put_replace_chassis_mac_flows(const struct simap *ct_zones,
>       }
>   }
>   
> +#define VLAN_80211AD_ETHTYPE 0x88a8
> +#define VLAN_80211Q_ETHTYPE 0x8100
> +
> +static void
> +ofpact_put_push_vlan(struct ofpbuf *ofpacts, const struct smap *options, int tag)
> +{
> +    const char *ethtype_opt = NULL;
> +    if (options) {
> +        ethtype_opt = smap_get(options, "ethtype");
> +    }
> +
> +    int ethtype = VLAN_80211Q_ETHTYPE;
> +    if (ethtype_opt) {
> +        if (!strcasecmp(ethtype_opt, "802.11ad")) {
> +            ethtype = VLAN_80211AD_ETHTYPE;
> +        } else if (strcasecmp(ethtype_opt, "802.11q")) {
> +            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> +            VLOG_WARN_RL(&rl, "Unknown port ethtype: %s", ethtype_opt);
> +        }
> +    }
> +
> +    struct ofpact_push_vlan *push_vlan;
> +    push_vlan = ofpact_put_PUSH_VLAN(ofpacts);
> +    push_vlan->ethertype = htons(ethtype);
> +
> +    struct ofpact_vlan_vid *vlan_vid;
> +    vlan_vid = ofpact_put_SET_VLAN_VID(ofpacts);
> +    vlan_vid->vlan_vid = tag;
> +    vlan_vid->push_vlan_if_needed = false;
> +}
> +
>   static void
>   put_replace_router_port_mac_flows(struct ovsdb_idl_index
>                                     *sbrec_port_binding_by_name,
> @@ -696,10 +727,7 @@ put_replace_router_port_mac_flows(struct ovsdb_idl_index
>           replace_mac->mac = chassis_mac;
>   
>           if (tag) {
> -            struct ofpact_vlan_vid *vlan_vid;
> -            vlan_vid = ofpact_put_SET_VLAN_VID(ofpacts_p);
> -            vlan_vid->vlan_vid = tag;
> -            vlan_vid->push_vlan_if_needed = true;
> +            ofpact_put_push_vlan(ofpacts_p, &localnet_port->options, tag);
>           }
>   
>           ofpact_put_OUTPUT(ofpacts_p)->port = ofport;
> @@ -1203,10 +1231,9 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
>           if (tag) {
>               /* For containers sitting behind a local vif, tag the packets
>                * before delivering them. */
> -            struct ofpact_vlan_vid *vlan_vid;
> -            vlan_vid = ofpact_put_SET_VLAN_VID(ofpacts_p);
> -            vlan_vid->vlan_vid = tag;
> -            vlan_vid->push_vlan_if_needed = true;
> +            ofpact_put_push_vlan(
> +                ofpacts_p, localnet_port ? &localnet_port->options : NULL,
> +                tag);
>           }
>           ofpact_put_OUTPUT(ofpacts_p)->port = ofport;
>           if (tag) {
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index 365d9e9c9..feb38b5d3 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -847,6 +847,12 @@
>             uses its local configuration to determine exactly how to connect to
>             this locally accessible network, if at all.
>           </column>
> +
> +        <column name="options" key="ethtype">
> +          Optional. VLAN EtherType field value for encapsulating VLAN
> +          headers. Supported values: 802.11q (default), 802.11ad.
> +        </column>
> +
>         </group>
>   
>         <group title="Options for l2gateway ports">
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 4c3d76d57..d00bac131 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -3227,6 +3227,91 @@ done
>   AT_CLEANUP
>   ])
>   
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([ovn -- VLAN transparency, passthru=true, multiple hosts, custom ethtype])
> +ovn_start
> +
> +ethtype=802.11ad
> +
> +check ovn-nbctl ls-add ls
> +check ovn-nbctl --wait=sb add Logical-Switch ls other_config vlan-passthru=true
> +
> +ln_port_name=ln-100
> +ovn-nbctl lsp-add ls $ln_port_name "" 100
> +ovn-nbctl lsp-set-addresses $ln_port_name unknown
> +ovn-nbctl lsp-set-type $ln_port_name localnet
> +ovn-nbctl lsp-set-options $ln_port_name network_name=phys-100 ethtype=$ethtype
> +net_add n-100
> +
> +# two hypervisors, each connected to the same network
> +for i in 1 2; do
> +    sim_add hv-$i
> +    as hv-$i
> +    ovs-vsctl add-br br-phys
> +    ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys-100:br-phys
> +    ovn_attach n-100 br-phys 192.168.0.$i
> +done
> +
> +for i in 1 2; do
> +    check ovn-nbctl lsp-add ls lsp$i
> +    check ovn-nbctl lsp-set-addresses lsp$i f0:00:00:00:00:0$i
> +done
> +
> +for i in 1 2; do
> +    as hv-$i
> +    ovs-vsctl add-port br-int vif$i -- set Interface vif$i external-ids:iface-id=lsp$i \
> +                                  options:tx_pcap=vif$i-tx.pcap \
> +                                  options:rxq_pcap=vif$i-rx.pcap \
> +                                  ofport-request=$i
> +    OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lsp$i` = xup])
> +done
> +
> +# create taps on fabric to check vlan encapsulation there
> +for i in 1 2; do
> +    as hv-$i
> +    ovs-vsctl add-port br-phys tap$i -- set Interface tap$i \
> +                                  options:tx_pcap=tap$i-tx.pcap \
> +                                  options:rxq_pcap=tap$i-rx.pcap
> +done
> +
> +for i in 1 2; do
> +    : > $i.expected
> +done
> +: > tap.expected
> +
> +test_packet() {
> +    local inport=$1 dst=$2 src=$3 eth=$4 lout=$5 ethtype=$6
> +
> +    # First try tracing the packet.
> +    uflow="inport==\"lsp$inport\" && eth.dst==$dst && eth.src==$src && eth.type==0x$eth && vlan.present==1"
> +    echo "output(\"$lout\");" > expout
> +    AT_CAPTURE_FILE([trace])
> +    AT_CHECK([ovn-trace --all ls "$uflow" | tee trace | sed '1,/Minimal trace/d'], [0], [expout])
> +
> +    # Then actually send a packet, for an end-to-end test.
> +    payload=fefefefe
> +    local packet=$(echo $dst$src | sed 's/://g')${eth}${payload}
> +    vif=vif$inport
> +    as hv-$1
> +    ovs-appctl netdev-dummy/receive $vif $packet
> +    echo $packet >> ${inport}.expected
> +
> +    phys_packet=$(echo $dst$src | sed 's/://g')${ethtype}0064${eth}${payload}
> +    echo $phys_packet >> tap.expected
> +}
> +
> +test_packet 1 f0:00:00:00:00:03 f0:00:00:00:00:01 8100 ln-100 88a8
> +test_packet 2 f0:00:00:00:00:03 f0:00:00:00:00:02 8100 ln-100 88a8
> +for i in 1 2; do
> +    OVN_CHECK_PACKETS_REMOVE_BROADCAST([vif$i-rx.pcap], [$i.expected])
> +done
> +for i in 1 2; do
> +    OVN_CHECK_PACKETS_REMOVE_BROADCAST([tap$i-tx.pcap], [tap.expected])
> +done
> +
> +AT_CLEANUP
> +])
> +
>   OVN_FOR_EACH_NORTHD([
>   AT_SETUP([ovn -- VLAN transparency, passthru=true, multiple hosts, flat/untagged])
>   ovn_start
>
Numan Siddique April 19, 2021, 1:09 p.m. UTC | #2
On Mon, Apr 19, 2021 at 8:21 AM Mark Michelson <mmichels@redhat.com> wrote:
>
> Acked-by: Mark Michelson <mmichels@redhat.com>

Thanks Ihar and Mark.

I applied this patch to the main branch with the below small changes

---
diff --git a/controller/physical.c b/controller/physical.c
index 6ed02df29c..96c959d184 100644
--- a/controller/physical.c
+++ b/controller/physical.c
@@ -615,10 +615,7 @@ put_replace_chassis_mac_flows(const struct simap *ct_zones,
 static void
 ofpact_put_push_vlan(struct ofpbuf *ofpacts, const struct smap
*options, int tag)
 {
-    const char *ethtype_opt = NULL;
-    if (options) {
-        ethtype_opt = smap_get(options, "ethtype");
-    }
+    const char *ethtype_opt = options ? smap_get(options, "ethtype") : NULL;

     int ethtype = VLAN_80211Q_ETHTYPE;
     if (ethtype_opt) {
diff --git a/tests/ovn.at b/tests/ovn.at
index d00bac131e..cfb57d607e 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -3259,11 +3259,11 @@ done

 for i in 1 2; do
     as hv-$i
-    ovs-vsctl add-port br-int vif$i -- set Interface vif$i
external-ids:iface-id=lsp$i \
+    check ovs-vsctl add-port br-int vif$i -- set Interface vif$i
external-ids:iface-id=lsp$i \
                                   options:tx_pcap=vif$i-tx.pcap \
                                   options:rxq_pcap=vif$i-rx.pcap \
                                   ofport-request=$i
-    OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lsp$i` = xup])
+    wait_for_ports_up lsp$i
 done

 # create taps on fabric to check vlan encapsulation there

-----

@Mark Michelson  My apologies for missing out your Acked-by tag while
applying the patch.

Thanks
Numan

>
> On 4/16/21 1:07 PM, Ihar Hrachyshka wrote:
> > In some environments, hardware serving the fabric network doesn't
> > support double 802.1q (0x8100) VLAN tags, but does support 802.11ad
> > (0x8a88) EthType for two layer VLAN traffic. Specifically, Cisco
> > hardware UCS VIC was identified affected by this limitation.
> >
> > With vlan-passthru=true set for a logical switch, VLAN tagged traffic
> > may be generated by VIFs. This patch allows to support the feature in
> > affected hardware environments.
> >
> > Signed-off-by: Ihar Hrachyshka <ihrachys@redhat.com>
> >
> > ---
> >
> > v1: initial version.
> > v2: fixed test scenario to actually validate packets sent by vifs.
> > v2: stylistic (spacing) change for a ternary operator.
> > v3: ux: switch to symbolic vlan ethtype representation.
> > ---
> >   NEWS                  |  1 +
> >   controller/physical.c | 43 ++++++++++++++++++----
> >   ovn-nb.xml            |  6 +++
> >   tests/ovn.at          | 85 +++++++++++++++++++++++++++++++++++++++++++
> >   4 files changed, 127 insertions(+), 8 deletions(-)
> >
> > diff --git a/NEWS b/NEWS
> > index 0e98d5e36..1ddde15f8 100644
> > --- a/NEWS
> > +++ b/NEWS
> > @@ -10,6 +10,7 @@ Post-v21.03.0
> >     - Introduced parallel processing in ovn-northd with the NB_Global config option
> >       'use_parallel_build' to enable it.  It is disabled by default.
> >     - Support vlan-passthru mode for tag=0 localnet ports.
> > +  - Support custom 802.11ad EthType for localnet ports.
> >
> >   OVN v21.03.0 - 12 Mar 2021
> >   -------------------------
> > diff --git a/controller/physical.c b/controller/physical.c
> > index 3710bf407..6ed02df29 100644
> > --- a/controller/physical.c
> > +++ b/controller/physical.c
> > @@ -609,6 +609,37 @@ put_replace_chassis_mac_flows(const struct simap *ct_zones,
> >       }
> >   }
> >
> > +#define VLAN_80211AD_ETHTYPE 0x88a8
> > +#define VLAN_80211Q_ETHTYPE 0x8100
> > +
> > +static void
> > +ofpact_put_push_vlan(struct ofpbuf *ofpacts, const struct smap *options, int tag)
> > +{
> > +    const char *ethtype_opt = NULL;
> > +    if (options) {
> > +        ethtype_opt = smap_get(options, "ethtype");
> > +    }
> > +
> > +    int ethtype = VLAN_80211Q_ETHTYPE;
> > +    if (ethtype_opt) {
> > +        if (!strcasecmp(ethtype_opt, "802.11ad")) {
> > +            ethtype = VLAN_80211AD_ETHTYPE;
> > +        } else if (strcasecmp(ethtype_opt, "802.11q")) {
> > +            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> > +            VLOG_WARN_RL(&rl, "Unknown port ethtype: %s", ethtype_opt);
> > +        }
> > +    }
> > +
> > +    struct ofpact_push_vlan *push_vlan;
> > +    push_vlan = ofpact_put_PUSH_VLAN(ofpacts);
> > +    push_vlan->ethertype = htons(ethtype);
> > +
> > +    struct ofpact_vlan_vid *vlan_vid;
> > +    vlan_vid = ofpact_put_SET_VLAN_VID(ofpacts);
> > +    vlan_vid->vlan_vid = tag;
> > +    vlan_vid->push_vlan_if_needed = false;
> > +}
> > +
> >   static void
> >   put_replace_router_port_mac_flows(struct ovsdb_idl_index
> >                                     *sbrec_port_binding_by_name,
> > @@ -696,10 +727,7 @@ put_replace_router_port_mac_flows(struct ovsdb_idl_index
> >           replace_mac->mac = chassis_mac;
> >
> >           if (tag) {
> > -            struct ofpact_vlan_vid *vlan_vid;
> > -            vlan_vid = ofpact_put_SET_VLAN_VID(ofpacts_p);
> > -            vlan_vid->vlan_vid = tag;
> > -            vlan_vid->push_vlan_if_needed = true;
> > +            ofpact_put_push_vlan(ofpacts_p, &localnet_port->options, tag);
> >           }
> >
> >           ofpact_put_OUTPUT(ofpacts_p)->port = ofport;
> > @@ -1203,10 +1231,9 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
> >           if (tag) {
> >               /* For containers sitting behind a local vif, tag the packets
> >                * before delivering them. */
> > -            struct ofpact_vlan_vid *vlan_vid;
> > -            vlan_vid = ofpact_put_SET_VLAN_VID(ofpacts_p);
> > -            vlan_vid->vlan_vid = tag;
> > -            vlan_vid->push_vlan_if_needed = true;
> > +            ofpact_put_push_vlan(
> > +                ofpacts_p, localnet_port ? &localnet_port->options : NULL,
> > +                tag);
> >           }
> >           ofpact_put_OUTPUT(ofpacts_p)->port = ofport;
> >           if (tag) {
> > diff --git a/ovn-nb.xml b/ovn-nb.xml
> > index 365d9e9c9..feb38b5d3 100644
> > --- a/ovn-nb.xml
> > +++ b/ovn-nb.xml
> > @@ -847,6 +847,12 @@
> >             uses its local configuration to determine exactly how to connect to
> >             this locally accessible network, if at all.
> >           </column>
> > +
> > +        <column name="options" key="ethtype">
> > +          Optional. VLAN EtherType field value for encapsulating VLAN
> > +          headers. Supported values: 802.11q (default), 802.11ad.
> > +        </column>
> > +
> >         </group>
> >
> >         <group title="Options for l2gateway ports">
> > diff --git a/tests/ovn.at b/tests/ovn.at
> > index 4c3d76d57..d00bac131 100644
> > --- a/tests/ovn.at
> > +++ b/tests/ovn.at
> > @@ -3227,6 +3227,91 @@ done
> >   AT_CLEANUP
> >   ])
> >
> > +OVN_FOR_EACH_NORTHD([
> > +AT_SETUP([ovn -- VLAN transparency, passthru=true, multiple hosts, custom ethtype])
> > +ovn_start
> > +
> > +ethtype=802.11ad
> > +
> > +check ovn-nbctl ls-add ls
> > +check ovn-nbctl --wait=sb add Logical-Switch ls other_config vlan-passthru=true
> > +
> > +ln_port_name=ln-100
> > +ovn-nbctl lsp-add ls $ln_port_name "" 100
> > +ovn-nbctl lsp-set-addresses $ln_port_name unknown
> > +ovn-nbctl lsp-set-type $ln_port_name localnet
> > +ovn-nbctl lsp-set-options $ln_port_name network_name=phys-100 ethtype=$ethtype
> > +net_add n-100
> > +
> > +# two hypervisors, each connected to the same network
> > +for i in 1 2; do
> > +    sim_add hv-$i
> > +    as hv-$i
> > +    ovs-vsctl add-br br-phys
> > +    ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys-100:br-phys
> > +    ovn_attach n-100 br-phys 192.168.0.$i
> > +done
> > +
> > +for i in 1 2; do
> > +    check ovn-nbctl lsp-add ls lsp$i
> > +    check ovn-nbctl lsp-set-addresses lsp$i f0:00:00:00:00:0$i
> > +done
> > +
> > +for i in 1 2; do
> > +    as hv-$i
> > +    ovs-vsctl add-port br-int vif$i -- set Interface vif$i external-ids:iface-id=lsp$i \
> > +                                  options:tx_pcap=vif$i-tx.pcap \
> > +                                  options:rxq_pcap=vif$i-rx.pcap \
> > +                                  ofport-request=$i
> > +    OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lsp$i` = xup])
> > +done
> > +
> > +# create taps on fabric to check vlan encapsulation there
> > +for i in 1 2; do
> > +    as hv-$i
> > +    ovs-vsctl add-port br-phys tap$i -- set Interface tap$i \
> > +                                  options:tx_pcap=tap$i-tx.pcap \
> > +                                  options:rxq_pcap=tap$i-rx.pcap
> > +done
> > +
> > +for i in 1 2; do
> > +    : > $i.expected
> > +done
> > +: > tap.expected
> > +
> > +test_packet() {
> > +    local inport=$1 dst=$2 src=$3 eth=$4 lout=$5 ethtype=$6
> > +
> > +    # First try tracing the packet.
> > +    uflow="inport==\"lsp$inport\" && eth.dst==$dst && eth.src==$src && eth.type==0x$eth && vlan.present==1"
> > +    echo "output(\"$lout\");" > expout
> > +    AT_CAPTURE_FILE([trace])
> > +    AT_CHECK([ovn-trace --all ls "$uflow" | tee trace | sed '1,/Minimal trace/d'], [0], [expout])
> > +
> > +    # Then actually send a packet, for an end-to-end test.
> > +    payload=fefefefe
> > +    local packet=$(echo $dst$src | sed 's/://g')${eth}${payload}
> > +    vif=vif$inport
> > +    as hv-$1
> > +    ovs-appctl netdev-dummy/receive $vif $packet
> > +    echo $packet >> ${inport}.expected
> > +
> > +    phys_packet=$(echo $dst$src | sed 's/://g')${ethtype}0064${eth}${payload}
> > +    echo $phys_packet >> tap.expected
> > +}
> > +
> > +test_packet 1 f0:00:00:00:00:03 f0:00:00:00:00:01 8100 ln-100 88a8
> > +test_packet 2 f0:00:00:00:00:03 f0:00:00:00:00:02 8100 ln-100 88a8
> > +for i in 1 2; do
> > +    OVN_CHECK_PACKETS_REMOVE_BROADCAST([vif$i-rx.pcap], [$i.expected])
> > +done
> > +for i in 1 2; do
> > +    OVN_CHECK_PACKETS_REMOVE_BROADCAST([tap$i-tx.pcap], [tap.expected])
> > +done
> > +
> > +AT_CLEANUP
> > +])
> > +
> >   OVN_FOR_EACH_NORTHD([
> >   AT_SETUP([ovn -- VLAN transparency, passthru=true, multiple hosts, flat/untagged])
> >   ovn_start
> >
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index 0e98d5e36..1ddde15f8 100644
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,7 @@  Post-v21.03.0
   - Introduced parallel processing in ovn-northd with the NB_Global config option
     'use_parallel_build' to enable it.  It is disabled by default.
   - Support vlan-passthru mode for tag=0 localnet ports.
+  - Support custom 802.11ad EthType for localnet ports.
 
 OVN v21.03.0 - 12 Mar 2021
 -------------------------
diff --git a/controller/physical.c b/controller/physical.c
index 3710bf407..6ed02df29 100644
--- a/controller/physical.c
+++ b/controller/physical.c
@@ -609,6 +609,37 @@  put_replace_chassis_mac_flows(const struct simap *ct_zones,
     }
 }
 
+#define VLAN_80211AD_ETHTYPE 0x88a8
+#define VLAN_80211Q_ETHTYPE 0x8100
+
+static void
+ofpact_put_push_vlan(struct ofpbuf *ofpacts, const struct smap *options, int tag)
+{
+    const char *ethtype_opt = NULL;
+    if (options) {
+        ethtype_opt = smap_get(options, "ethtype");
+    }
+
+    int ethtype = VLAN_80211Q_ETHTYPE;
+    if (ethtype_opt) {
+        if (!strcasecmp(ethtype_opt, "802.11ad")) {
+            ethtype = VLAN_80211AD_ETHTYPE;
+        } else if (strcasecmp(ethtype_opt, "802.11q")) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+            VLOG_WARN_RL(&rl, "Unknown port ethtype: %s", ethtype_opt);
+        }
+    }
+
+    struct ofpact_push_vlan *push_vlan;
+    push_vlan = ofpact_put_PUSH_VLAN(ofpacts);
+    push_vlan->ethertype = htons(ethtype);
+
+    struct ofpact_vlan_vid *vlan_vid;
+    vlan_vid = ofpact_put_SET_VLAN_VID(ofpacts);
+    vlan_vid->vlan_vid = tag;
+    vlan_vid->push_vlan_if_needed = false;
+}
+
 static void
 put_replace_router_port_mac_flows(struct ovsdb_idl_index
                                   *sbrec_port_binding_by_name,
@@ -696,10 +727,7 @@  put_replace_router_port_mac_flows(struct ovsdb_idl_index
         replace_mac->mac = chassis_mac;
 
         if (tag) {
-            struct ofpact_vlan_vid *vlan_vid;
-            vlan_vid = ofpact_put_SET_VLAN_VID(ofpacts_p);
-            vlan_vid->vlan_vid = tag;
-            vlan_vid->push_vlan_if_needed = true;
+            ofpact_put_push_vlan(ofpacts_p, &localnet_port->options, tag);
         }
 
         ofpact_put_OUTPUT(ofpacts_p)->port = ofport;
@@ -1203,10 +1231,9 @@  consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
         if (tag) {
             /* For containers sitting behind a local vif, tag the packets
              * before delivering them. */
-            struct ofpact_vlan_vid *vlan_vid;
-            vlan_vid = ofpact_put_SET_VLAN_VID(ofpacts_p);
-            vlan_vid->vlan_vid = tag;
-            vlan_vid->push_vlan_if_needed = true;
+            ofpact_put_push_vlan(
+                ofpacts_p, localnet_port ? &localnet_port->options : NULL,
+                tag);
         }
         ofpact_put_OUTPUT(ofpacts_p)->port = ofport;
         if (tag) {
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 365d9e9c9..feb38b5d3 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -847,6 +847,12 @@ 
           uses its local configuration to determine exactly how to connect to
           this locally accessible network, if at all.
         </column>
+
+        <column name="options" key="ethtype">
+          Optional. VLAN EtherType field value for encapsulating VLAN
+          headers. Supported values: 802.11q (default), 802.11ad.
+        </column>
+
       </group>
 
       <group title="Options for l2gateway ports">
diff --git a/tests/ovn.at b/tests/ovn.at
index 4c3d76d57..d00bac131 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -3227,6 +3227,91 @@  done
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ovn -- VLAN transparency, passthru=true, multiple hosts, custom ethtype])
+ovn_start
+
+ethtype=802.11ad
+
+check ovn-nbctl ls-add ls
+check ovn-nbctl --wait=sb add Logical-Switch ls other_config vlan-passthru=true
+
+ln_port_name=ln-100
+ovn-nbctl lsp-add ls $ln_port_name "" 100
+ovn-nbctl lsp-set-addresses $ln_port_name unknown
+ovn-nbctl lsp-set-type $ln_port_name localnet
+ovn-nbctl lsp-set-options $ln_port_name network_name=phys-100 ethtype=$ethtype
+net_add n-100
+
+# two hypervisors, each connected to the same network
+for i in 1 2; do
+    sim_add hv-$i
+    as hv-$i
+    ovs-vsctl add-br br-phys
+    ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys-100:br-phys
+    ovn_attach n-100 br-phys 192.168.0.$i
+done
+
+for i in 1 2; do
+    check ovn-nbctl lsp-add ls lsp$i
+    check ovn-nbctl lsp-set-addresses lsp$i f0:00:00:00:00:0$i
+done
+
+for i in 1 2; do
+    as hv-$i
+    ovs-vsctl add-port br-int vif$i -- set Interface vif$i external-ids:iface-id=lsp$i \
+                                  options:tx_pcap=vif$i-tx.pcap \
+                                  options:rxq_pcap=vif$i-rx.pcap \
+                                  ofport-request=$i
+    OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lsp$i` = xup])
+done
+
+# create taps on fabric to check vlan encapsulation there
+for i in 1 2; do
+    as hv-$i
+    ovs-vsctl add-port br-phys tap$i -- set Interface tap$i \
+                                  options:tx_pcap=tap$i-tx.pcap \
+                                  options:rxq_pcap=tap$i-rx.pcap
+done
+
+for i in 1 2; do
+    : > $i.expected
+done
+: > tap.expected
+
+test_packet() {
+    local inport=$1 dst=$2 src=$3 eth=$4 lout=$5 ethtype=$6
+
+    # First try tracing the packet.
+    uflow="inport==\"lsp$inport\" && eth.dst==$dst && eth.src==$src && eth.type==0x$eth && vlan.present==1"
+    echo "output(\"$lout\");" > expout
+    AT_CAPTURE_FILE([trace])
+    AT_CHECK([ovn-trace --all ls "$uflow" | tee trace | sed '1,/Minimal trace/d'], [0], [expout])
+
+    # Then actually send a packet, for an end-to-end test.
+    payload=fefefefe
+    local packet=$(echo $dst$src | sed 's/://g')${eth}${payload}
+    vif=vif$inport
+    as hv-$1
+    ovs-appctl netdev-dummy/receive $vif $packet
+    echo $packet >> ${inport}.expected
+
+    phys_packet=$(echo $dst$src | sed 's/://g')${ethtype}0064${eth}${payload}
+    echo $phys_packet >> tap.expected
+}
+
+test_packet 1 f0:00:00:00:00:03 f0:00:00:00:00:01 8100 ln-100 88a8
+test_packet 2 f0:00:00:00:00:03 f0:00:00:00:00:02 8100 ln-100 88a8
+for i in 1 2; do
+    OVN_CHECK_PACKETS_REMOVE_BROADCAST([vif$i-rx.pcap], [$i.expected])
+done
+for i in 1 2; do
+    OVN_CHECK_PACKETS_REMOVE_BROADCAST([tap$i-tx.pcap], [tap.expected])
+done
+
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([ovn -- VLAN transparency, passthru=true, multiple hosts, flat/untagged])
 ovn_start