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
