VLAN Trunking and Inter-VLAN Routing
This guide picks up where our CISCO VLANs Configuration guide left off. There, we created VLANs and isolated three departments. Here, we'll look at how trunks actually carry multiple VLANs over a single link, how to lock those trunks down against common attacks, and then we'll let the traffic between VLANs flow safely through a router or a Layer-3 switch.
Real-World Scenario
Sales (VLAN 10) needs to reach the internal Confluence wiki living in the Server VLAN (VLAN 50), but Guest (VLAN 30) must not. The switches keep the broadcast domains apart; we'll let a router (or a Layer-3 switch) selectively route between them and an ACL decide who can talk to whom.
802.1Q Tagging in Detail
IEEE 802.1Q (the canonical reference is the IEEE 802.1Q-2018 standard) defines how multiple VLANs share a single Ethernet link. When a switch sends a frame onto a trunk, it inserts a 4-byte VLAN tag between the source MAC address and the EtherType/length field of the original frame. The tag breaks down like this:
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | TPID = 0x8100 (16 bits) | PCP |D| VID (12 bits) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- TPID — Tag Protocol Identifier (16 bits, 0x8100) tells the receiver "what follows is a 802.1Q tag."
- PCP — Priority Code Point (3 bits) carries 802.1p class-of-service (0 best-effort, 7 highest).
- DEI — Drop Eligible Indicator (1 bit) marks a frame as droppable under congestion.
- VID — VLAN Identifier (12 bits) gives 212 = 4096 possible VLAN IDs (0 and 4095 are reserved, leaving 4094 usable).
The tag adds 4 bytes to the frame, which is why trunk-capable switches accept a slightly larger MTU on trunks (1522 bytes baseline frame size). The frame's CRC is recomputed after insertion. Crucially, the original IP packet is not modified — VLAN tagging happens entirely at Layer 2.
Native VLAN: Behavior and Pitfalls
On a trunk, one VLAN is special: the native VLAN. Frames in the native VLAN are sent untagged. This exists for backward compatibility with hubs and unmanaged devices that don't understand 802.1Q. It also creates two problems we need to solve.
Pitfall 1: Native VLAN Mismatch
If two ends of a trunk disagree on the native VLAN, frames traveling untagged from one side will be silently dropped or, worse, leak into the wrong VLAN on the other side. Cisco's CDP/LLDP will emit a log line, but only periodically:
%CDP-4-NATIVE_VLAN_MISMATCH: Native VLAN mismatch discovered on GigabitEthernet0/1 (999), with PeerSwitch GigabitEthernet0/1 (1).
Pitfall 2: Native VLAN Hopping (Double Tagging)
An attacker on an access port in the native VLAN can craft a frame with two 802.1Q tags stacked. The first switch strips the outer tag (because the frame was on the native VLAN) and forwards what's left over the trunk. The inner tag (pointing at a victim VLAN) now reaches the next switch, which delivers the frame inside that VLAN. This is a one-way attack — replies don't come back — but it's enough for malicious broadcasts, ARP poisoning, or sending crafted packets to victims.
The defence:
- Pick an unused, "parked" VLAN as the native VLAN on every trunk (we like 999, labelled
BLACKHOLE). - Make sure no access port is ever assigned to that native VLAN.
- Force the switch to tag the native VLAN too — that defeats the double-tagging trick entirely.
Pin and tag the native VLAN
! Global: tag the native VLAN on all dot1q trunks Switch(config)# vlan dot1q tag native ! Per-trunk Switch(config)# interface Gi0/1 Switch(config-if)# switchport mode trunk Switch(config-if)# switchport trunk encapsulation dot1q ! On platforms that support multiple encaps Switch(config-if)# switchport trunk native vlan 999 Switch(config-if)# switchport trunk allowed vlan 10,20,30,999 Switch(config-if)# switchport nonegotiate
DTP and Why You Should Disable It
Dynamic Trunking Protocol is a Cisco-proprietary feature that lets two switches negotiate whether a link becomes a trunk. On default Cisco switchports it leaves you exposed to switch spoofing: an attacker plugs into a "dynamic auto" or "dynamic desirable" port, sends DTP packets pretending to be a switch, the port flips to trunk, and the attacker now sees every VLAN on the box.
Always hardcode each port:
Disable DTP and pin port mode
! Trunk ports Switch(config-if)# switchport mode trunk Switch(config-if)# switchport nonegotiate ! stops DTP frames ! Access ports Switch(config-if)# switchport mode access Switch(config-if)# switchport access vlan 10 Switch(config-if)# switchport nonegotiate
The Allowed VLAN List
By default a trunk carries every VLAN that exists on the switch. That's noise we don't want — broadcasts from unrelated VLANs eat trunk bandwidth and widen the failure domain. Always prune to the minimum set:
Restricting a trunk to known VLANs
Switch(config-if)# switchport trunk allowed vlan 10,20,30,999 ! Add to the existing list (don't replace it) Switch(config-if)# switchport trunk allowed vlan add 50 ! Remove one Switch(config-if)# switchport trunk allowed vlan remove 30 ! Verify Switch# show interfaces trunk Switch# show interfaces Gi0/1 trunk
VTP: Safest in Transparent or Off Mode
VLAN Trunking Protocol synchronizes the VLAN database across all switches in a VTP domain. It can be convenient on small networks; on larger ones it has historically been a footgun — a newly added switch with a higher configuration revision number can overwrite the entire domain's VLAN database, wiping VLANs across the whole network.
VTP modes:
- Server – Can create/modify/delete VLANs and propagates them to clients (default on older IOS).
- Client – Receives VLAN updates only, cannot edit locally.
- Transparent – Ignores incoming VTP, has its own local VLAN database, forwards VTP traffic. This is what we usually want.
- Off (VTPv3 only) – Doesn't process or forward VTP at all. Even safer where supported.
Use VTP transparent (or off)
Switch(config)# vtp mode transparent Switch(config)# vtp domain LAB ! On VTPv3-capable IOS: Switch(config)# vtp version 3 Switch(config)# vtp mode off
Lab Topology for Inter-VLAN Routing
We'll keep the three department VLANs from the previous guide (Sales 10, Engineering 20, Guest 30) and add an internal server VLAN (50) on a Layer-3 device. We're going to compare two ways of doing the inter-VLAN routing: router-on-a-stick and a Layer-3 switch with SVIs.
+---------------------+
| Router / L3 Switch |
| (R1) |
+----------+----------+
| Gi0/0 (Trunk: 10,20,30,50,999 native)
+----------+----------+
| Switch1 |
+----+----+-----+-----+
| | |
Gi0/1 Gi0/2 Gi0/3 (access)
V10 V20 V30
| | |
PC1 PC2 PC3
Address plan:
- VLAN 10 (Sales): 192.168.10.0/24, gateway
.1, PC1 =.5 - VLAN 20 (Engineering): 192.168.20.0/24, gateway
.1, PC2 =.5 - VLAN 30 (Guest): 192.168.30.0/24, gateway
.1, PC3 =.5 - VLAN 50 (Servers): 192.168.50.0/24, gateway
.1
Router-on-a-Stick (Subinterfaces)
A traditional Cisco IOS router with one physical interface can route between VLANs by carving its physical interface into subinterfaces, each tagged with a different VLAN. The switch sees the link as a trunk; the router sees N virtual interfaces.
On the switch (Switch1):
Switch side: trunk up to the router
Switch1(config)# interface Gi0/0 Switch1(config-if)# description TRUNK to R1 Gi0/0 Switch1(config-if)# switchport trunk encapsulation dot1q Switch1(config-if)# switchport mode trunk Switch1(config-if)# switchport trunk native vlan 999 Switch1(config-if)# switchport trunk allowed vlan 10,20,30,50,999 Switch1(config-if)# switchport nonegotiate
On the router (R1):
Router side: subinterfaces per VLAN
R1(config)# interface Gi0/0 R1(config-if)# no shutdown R1(config-if)# exit R1(config)# interface Gi0/0.10 R1(config-subif)# description Sales R1(config-subif)# encapsulation dot1q 10 R1(config-subif)# ip address 192.168.10.1 255.255.255.0 R1(config)# interface Gi0/0.20 R1(config-subif)# description Engineering R1(config-subif)# encapsulation dot1q 20 R1(config-subif)# ip address 192.168.20.1 255.255.255.0 R1(config)# interface Gi0/0.30 R1(config-subif)# description Guest R1(config-subif)# encapsulation dot1q 30 R1(config-subif)# ip address 192.168.30.1 255.255.255.0 R1(config)# interface Gi0/0.50 R1(config-subif)# description Servers R1(config-subif)# encapsulation dot1q 50 R1(config-subif)# ip address 192.168.50.1 255.255.255.0
Why this works: the router treats each subinterface as a real Layer-3 interface. When PC1 (VLAN 10) sends a frame to its default gateway 192.168.10.1, the switch tags the frame for VLAN 10, the router receives it on Gi0/0.10, looks up the destination IP in its routing table, then sends the frame out (for example) Gi0/0.50 with a VLAN 50 tag. The switch delivers it to the server.
Each PC sets its default gateway to the matching .1. We don't need a routing protocol; the connected routes are enough.
Layer-3 Switch with SVIs
On a multilayer (L3) switch, we don't need a router at all. We enable IP routing and create a Switch Virtual Interface (SVI) per VLAN:
L3 switch: routed SVIs
L3Switch(config)# ip routing ! Enable Layer-3 forwarding L3Switch(config)# vlan 10 L3Switch(config-vlan)# name SALES L3Switch(config-vlan)# exit L3Switch(config)# vlan 20 L3Switch(config-vlan)# name ENGINEERING L3Switch(config-vlan)# exit L3Switch(config)# vlan 30 L3Switch(config-vlan)# name GUEST L3Switch(config-vlan)# exit L3Switch(config)# vlan 50 L3Switch(config-vlan)# name SERVERS L3Switch(config-vlan)# exit L3Switch(config)# interface vlan 10 L3Switch(config-if)# ip address 192.168.10.1 255.255.255.0 L3Switch(config-if)# no shutdown L3Switch(config)# interface vlan 20 L3Switch(config-if)# ip address 192.168.20.1 255.255.255.0 L3Switch(config-if)# no shutdown L3Switch(config)# interface vlan 30 L3Switch(config-if)# ip address 192.168.30.1 255.255.255.0 L3Switch(config-if)# no shutdown L3Switch(config)# interface vlan 50 L3Switch(config-if)# ip address 192.168.50.1 255.255.255.0 L3Switch(config-if)# no shutdown
An SVI comes up only when at least one switchport in that VLAN is in the spanning-tree forwarding state. Plug a host in or leave at least one access port assigned and active. With ip routing on, the switch's hardware ASIC handles inter-VLAN forwarding at line rate — generally much faster than router-on-a-stick.
Where Do End Hosts Point Their Default Gateway?
Each host's default gateway is the IP of the SVI (or subinterface) in its own VLAN:
- PC1 (VLAN 10) gateway →
192.168.10.1 - PC2 (VLAN 20) gateway →
192.168.20.1 - PC3 (VLAN 30) gateway →
192.168.30.1
Hosts never know about the other VLANs — they just send any non-local traffic to their gateway, and the router/L3 switch handles the rest.
Verification
Switch side
Switch# show vlan brief Switch# show interfaces trunk Switch# show interfaces Gi0/0 switchport Switch# show interfaces Gi0/0 trunk
Router / L3 switch
R1# show ip interface brief R1# show ip route R1# show ip route connected R1# show interfaces Gi0/0.10 R1# show vlans ! On routers running subinterfaces
From the PCs
PC1> ping 192.168.10.1 ! Should succeed (its own gateway) PC1> ping 192.168.50.10 ! Should succeed (cross-VLAN, via R1) PC1> tracert 192.168.50.10 ! First hop should be 192.168.10.1
Restricting Who Can Talk to Whom
Once we enable inter-VLAN routing, everyone can talk to everyone. That's rarely the policy we want. Apply an ACL on the gateway interface to allow only what we mean to allow. The Guest VLAN, for instance, should reach the internet but not the server VLAN.
Sample Guest ACL on the L3 switch
L3Switch(config)# ip access-list extended GUEST-IN L3Switch(config-ext-nacl)# deny ip any 192.168.10.0 0.0.0.255 L3Switch(config-ext-nacl)# deny ip any 192.168.20.0 0.0.0.255 L3Switch(config-ext-nacl)# deny ip any 192.168.50.0 0.0.0.255 L3Switch(config-ext-nacl)# permit ip any any L3Switch(config-ext-nacl)# exit L3Switch(config)# interface vlan 30 L3Switch(config-if)# ip access-group GUEST-IN in
Apply it in on the Guest SVI: that filters traffic entering the router from the Guest VLAN, which is the only direction we need to restrict.
Common Troubleshooting
Hosts can ping their gateway but not across VLANs
- Confirm
ip routingis enabled (L3 switch). - Confirm the SVI or subinterface is up:
show ip interface brief. - Look for ACLs that might drop the traffic:
show access-lists,show ip interface vlan 10. - From the host, run
tracert— the first hop should be the local gateway, not the host's own address.
Some VLANs reach the router but others don't
- The trunk's allowed list is probably missing those VLAN IDs. Check
show interfaces Gi0/0 trunk. - The subinterface may be missing or have the wrong
encapsulation dot1qtag.
Intermittent drops, native VLAN warnings in logs
- Almost always a native-VLAN mismatch. Match both ends and prefer to
vlan dot1q tag nativeglobally.
Summary
802.1Q gives us 4094 usable VLAN IDs on a single physical link, but the defaults are unsafe: DTP-negotiated trunks, VLAN 1 as the native, and a wide-open allowed list. The hardened recipe is the same every time — hardcode trunk mode, pin native VLAN to an unused ID and tag it, prune the allowed list, and either drop a router-on-a-stick or an L3 switch into the topology when we need to route between VLANs. ACLs on the SVI then decide which conversations are allowed.