Catalyst 3850: trunks with a native VLAN that does not exist
In short. Every trunk gets
switchport trunk native vlan 998, and VLAN 998 is never created. An untagged frame on a trunk has no VLAN to join and is dropped. The provider's handoff is an access port in VLAN 999, and 999 rides the firewall port-channels as one more tagged VLAN.show interfaces trunkconfirms the native, and a capture on the firewall's WAN confirms the provider's frames arrive tagged 999.
I want untagged frames on a trunk to go nowhere, and the WAN to reach both firewalls
Two things people search for in the same week. First, how to stop a forgotten tag from putting a server in VLAN 1. Second, how to get one provider cable to two firewalls without a second cable or an extra switch. The answer to both is the same trunk discipline. The addressing scheme that the VLAN numbers come from is in the decision post at the end.
What you need
- A WS-C3850-12X48U stack on IOS-XE 16.12.
- A VLAN plan where 998 and 999 are reserved: 998 is the poison native and is never created; 999 is WAN and is created.
- Two firewalls, each with a cross-stack LACP port-channel to the stack. In the examples they are
fw1andfw2onPort-channel11andPort-channel12. - The provider handoff cabled to one stack member, here
GigabitEthernet1/0/48.
flowchart LR
ISP["Provider handoff<br/>untagged on their side"] -- "Gi1/0/48 access VLAN 999" --> ST["Catalyst stack<br/>native 998 on every trunk"]
ST -- "Po11 trunk, all VLANs incl. 999" --> FW1["fw1<br/>WAN = VLAN 999 on the LAG"]
ST -- "Po12 trunk, all VLANs incl. 999" --> FW2["fw2<br/>WAN = VLAN 999 on the LAG"]Steps
- Create the VLANs you actually use, and only those. VLAN 999 is in the list. VLAN 998 is not, and will never be.
sw-a.site.example(config)# vlan 999
sw-a.site.example(config-vlan)# name WAN
sw-a.site.example(config-vlan)# exit
- On every trunk, set the native VLAN to 998 and list the allowed VLANs explicitly. The firewall port-channels carry everything because the firewalls are the gateway for everything, sync included.
sw-a.site.example(config)# interface Port-channel11
sw-a.site.example(config-if)# description fw1 lagg0
sw-a.site.example(config-if)# switchport mode trunk
sw-a.site.example(config-if)# switchport trunk native vlan 998
sw-a.site.example(config-if)# switchport trunk allowed vlan 1,2,5,10,11,100-199,999
sw-a.site.example(config-if)# switchport nonegotiate
sw-a.site.example(config-if)# spanning-tree portfast trunk
Repeat for Port-channel12, and set the same two switchport trunk lines on the physical members of each. A Proxmox node's trunk gets the same native and a shorter allowed list without 2 and 999.
- Configure the provider port as an access port in VLAN 999. The provider sends untagged frames; the switch tags them 999 on the way in and strips the tag on the way out. Turn off everything chatty towards a device you do not own.
sw-a.site.example(config)# interface GigabitEthernet1/0/48
sw-a.site.example(config-if)# description transit handoff
sw-a.site.example(config-if)# switchport mode access
sw-a.site.example(config-if)# switchport access vlan 999
sw-a.site.example(config-if)# switchport nonegotiate
sw-a.site.example(config-if)# no cdp enable
sw-a.site.example(config-if)# no lldp transmit
sw-a.site.example(config-if)# spanning-tree portfast
sw-a.site.example(config-if)# end
sw-a.site.example# write memory
Where a site has two independent transit uplinks, the second goes to the other member as its own access port in its own WAN VLAN. They are not bundled: a LAG implies one provider on both ends, and independent providers need independent ports so a failure shows as link-down rather than hiding in a half-working bundle.
-
On each firewall, the WAN interface is VLAN 999 on the LACP aggregate. In OPNsense that is a VLAN interface with parent
lagg0and tag 999, assigned as WAN, with the public addressing from the provider on it and CARP for the shared address if you run a pair. Nothing on the firewall knows there is a switch between it and the provider. -
Decide whether you want the native VLAN tagged as well.
vlan dot1q tag nativein global configuration makes the switch tag frames in the native VLAN on every trunk and drop untagged frames on ingress. With a native VLAN that does not exist it changes nothing in practice, and I leave it off, but it is the belt to the braces if someone creates 998 by accident one day.
Verify it worked
Confirm the trunk state. Native should read 998 on every trunk and 998 must not appear in the forwarding list, because it has no members:
sw-a.site.example# show interfaces trunk
Port Mode Encapsulation Status Native vlan
Po11 on 802.1q trunking 998
Po12 on 802.1q trunking 998
Port Vlans allowed on trunk
Po11 1-2,5,10-11,100-199,999
Po12 1-2,5,10-11,100-199,999
Port Vlans in spanning tree forwarding state and not pruned
Po11 1-2,5,10-11,100-199,999
Po12 1-2,5,10-11,100-199,999
show vlan brief should list 999 with Gi1/0/48 as a member and should not list 998 at all. show mac address-table vlan 999 should show the provider's router MAC on Gi1/0/48 and both firewalls' MACs on their port-channels.
On the firewall, capture on the parent aggregate with the tag visible, and you should see the provider's gateway ARP replies and your own traffic carrying tag 999:
tcpdump -eni lagg0 vlan 999 and arp
Then from inside, trace to an external address and confirm it leaves via the firewall's VLAN 999 interface.
Gotchas
- Never create VLAN 998. It is not a configuration line to add later; its absence is the configuration. A
vlan 998typed to "see what happens" joins every untagged mistake in the estate into one broadcast domain. show interfaces trunkreports the administrative native VLAN. A trunk that was negotiated by DTP can differ operationally;switchport nonegotiateeverywhere makes the administrative state the real state.- CDP and LLDP log
%CDP-4-NATIVE_VLAN_MISMATCHwhen a neighbouring switch has a different native. It is a useful warning between switches, and noise from a firewall or hypervisor that does not run either. Disable them on provider and host ports. - The provider port is an access port, so the provider never sees a tag. If their handoff is itself tagged, the port becomes a trunk with their VLAN allowed and 998 as native, and the firewall VLAN interface uses their tag. Ask before cabling.
- Spanning tree still runs on the WAN VLAN. If the provider's gear sends BPDUs, a plain access port will participate.
portfastkeeps the port forwarding quickly, andspanning-tree bpduguard enableon the provider port is reasonable once you have confirmed they send none.
FAQ
Why not just use VLAN 1 as native and keep it empty? VLAN 1 cannot be deleted and it carries control protocols by default. Something always ends up in it. A number that does not exist cannot accumulate anything.
Does putting the WAN through the switch add a failure point? It replaces a dependency on one firewall's physical port with a dependency on the stack, which both firewalls depend on anyway. A firewall can now be swapped without touching the provider's cable, which has mattered more often than a stack failure has.
What about the sync VLAN between the firewalls? VLAN 2 rides the same port-channels. At the first site the firewalls used a crossover cable; it moved onto the stack when the second site needed the same pattern with no spare ports.