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:

  1. Pick an unused, "parked" VLAN as the native VLAN on every trunk (we like 999, labelled BLACKHOLE).
  2. Make sure no access port is ever assigned to that native VLAN.
  3. 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 routing is 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 dot1q tag.

Intermittent drops, native VLAN warnings in logs

  • Almost always a native-VLAN mismatch. Match both ends and prefer to vlan dot1q tag native globally.

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.