VLANs Explained: Two Separate Networks on One Wire with 802.1Q Tags
Put two VLANs on a single physical link, ping across each, and watch 802.1Q tags keep them completely isolated — one wire, two broadcast domains.
This post is part of Protocol Lab, a free, hands-on series for learning networking protocols by building and breaking them in a container lab. All the lab material — topologies, configs, and scripts — lives in the repo: github.com/pathvector-studio/protocol-lab.
One physical link normally carries one Layer-2 network — one broadcast domain. 802.1Q VLANs let a single link carry many separate LANs at once. Each frame gets a small tag carrying a VLAN ID (VID); the tag says which virtual LAN the frame belongs to, and devices only see frames for their own VLAN.
Reading guide: rfc-notes/vlan-8021q.md
Prerequisites: Lab 24: ARP, Lab 18: VXLAN
Expected time: 40–55 minutes.
The Goal
This lab puts two VLANs on one link and watches the tags do their job:
node-aandnode-bshare one link, and each has a subinterface on VLAN 100 (10.0.100.x) and VLAN 200 (10.0.200.x).- A ping over VLAN 100 travels the link as an 802.1Q-tagged frame with
vlan 100. - A ping over VLAN 200 is tagged
vlan 200. - During a VLAN-100 ping, the wire shows only
vlan 100frames — VLAN 200 never sees them.
By the end, you should be able to explain this table:
| Ping | Tag on the wire | Seen by |
|---|---|---|
over VLAN 100 (10.0.100.2) |
802.1Q, vlan 100 |
only VLAN 100 subinterfaces |
over VLAN 200 (10.0.200.2) |
802.1Q, vlan 200 |
only VLAN 200 subinterfaces |
What You Will Learn
- What a VLAN is: a way to slice one physical LAN into several isolated virtual LANs.
- What an 802.1Q tag contains (a VLAN ID) and where it sits in the frame.
- What a trunk link is (a link carrying tagged frames for multiple VLANs).
- Why two VLANs on the same wire cannot reach each other (separate broadcast domains).
- How this compares to VXLAN (Lab 18): both isolate L2, but VLAN tags a local link (12-bit VID), while VXLAN tunnels across L3 (24-bit VNI).
This lab does not cover:
- VLAN trunking protocols (VTP), spanning tree, or switch configuration.
- Access ports vs trunk ports on real switches (here both ends are "trunks").
- Private VLANs, QinQ (stacked tags), or inter-VLAN routing.
Where to Read in the Standards
VLANs are defined not in an IETF RFC but in IEEE 802.1Q. Here are the standard document and the related RFCs:
| Reference | What to focus on |
|---|---|
| IEEE 802.1Q | The VLAN tag format (TPID 0x8100, PCP, 12-bit VID) and the trunk concept |
| RFC 5517 | Private VLANs (for reference — an extension of the isolation idea) |
| RFC 826 | ARP (L2 resolution within a VLAN; each VLAN is its own broadcast domain) |
| RFC 5737 | Confirming the addresses used here are documentation-only |
The Big Picture
node-a and node-b are connected by a single link, and each node gets a subinterface for VLAN 100 and VLAN 200.
node-a ==== eth1/eth1 (trunk) ==== node-b
eth1.100 10.0.100.1 eth1.100 10.0.100.2 (VLAN 100)
eth1.200 10.0.200.1 eth1.200 10.0.200.2 (VLAN 200)
Pinging over VLAN 100 sends frames tagged vlan 100 across the wire, and they never mix with VLAN 200.
flowchart LR
subgraph node-a
a1["eth1.100<br/>10.0.100.1"]
a2["eth1.200<br/>10.0.200.1"]
end
subgraph node-b
b1["eth1.100<br/>10.0.100.2"]
b2["eth1.200<br/>10.0.200.2"]
end
a1 -- "802.1Q vlan 100" --- b1
a2 -- "802.1Q vlan 200" --- b2
note["One shared eth1 (trunk).<br/>Tags split it into two LANs"]
Note: The
10.0.0.0/8subnets are a local, closed range — nothing in this lab touches the real internet.
What You Need
Recommended environment:
- Linux / WSL2 / a Linux VM
- Docker
- containerlab
Images used:
nicolaka/netshoot:latest— bundlesip(with VLAN support),ping, andtcpdump(with 802.1Q decoding).
No additional images are required. The VLAN subinterfaces are created by run.sh.
Running the Lab
The quick path, which deploys, verifies, and tears down for you:
./scripts/labctl.sh run vlan-26
Or step through it manually:
1. Move into the working directory
cd protocol-lab/examples/vlan-26
2. Deploy and create the VLANs
sudo containerlab deploy -t vlan-26.clab.yml
# VLAN 100 / 200 subinterfaces on each node
docker exec clab-vlan-26-node-a sh -c '
ip link add link eth1 name eth1.100 type vlan id 100
ip link add link eth1 name eth1.200 type vlan id 200
ip addr add 10.0.100.1/24 dev eth1.100; ip link set eth1.100 up
ip addr add 10.0.200.1/24 dev eth1.200; ip link set eth1.200 up'
# node-b gets the symmetric setup (with .2 addresses)
3. Ping over each VLAN and look at the tags
docker exec -d clab-vlan-26-node-a tcpdump -i eth1 -n -e "icmp"
docker exec clab-vlan-26-node-a ping -c2 10.0.100.2 # VLAN 100
docker exec clab-vlan-26-node-a ping -c2 10.0.200.2 # VLAN 200
What to look for:
... ethertype 802.1Q (0x8100) ... vlan 100, ... 10.0.100.1 > 10.0.100.2: ICMP echo request
... ethertype 802.1Q (0x8100) ... vlan 200, ... 10.0.200.1 > 10.0.200.2: ICMP echo request
Both flows travel the same eth1, yet the tags (vlan 100 / vlan 200) keep them distinguishable.
4. Confirm the isolation
# While pinging over VLAN 100, capture only vlan 100
docker exec -d clab-vlan-26-node-a tcpdump -i eth1 -n -e "vlan 100 and icmp"
docker exec clab-vlan-26-node-a ping -c2 10.0.100.2
No vlan 200 frames ever appear in the VLAN-100 capture. Each VLAN is its own broadcast domain.
Expected Output
- Pings succeed over both VLAN 100 and VLAN 200.
- The trunk (
eth1) capture shows both802.1Q ... vlan 100andvlan 200frames. - The VLAN-100-only capture shows
vlan 100frames exclusively (vlan 200is absent).
Why It Works
A VLAN (Virtual LAN) logically splits one physical L2 network into multiple independent LANs. Think of an office separating the "sales LAN" from the "engineering LAN" over the same cabling and the same switches.
- The 802.1Q tag. A 4-byte tag is inserted into each Ethernet frame right after the source MAC. It carries the VLAN ID (VID, 12 bits = up to 4094), declaring which VLAN the frame belongs to. Tagged frames are identified by
ethertype 0x8100(802.1Q). - Trunk. A link that carries tagged frames for multiple VLANs is called a trunk. This lab's
eth1is a trunk carrying both VLAN 100 and VLAN 200 as tagged frames. The receiver looks at the tag and demultiplexes each frame to the matching subinterface (eth1.100/eth1.200). - Isolation (broadcast domains). A VLAN-100 frame is delivered only to VLAN-100 interfaces, never to VLAN-200 ones. So VLAN 100 and VLAN 200 are separate broadcast domains even though they share the same wire. ARP broadcasts (Lab 24) stay contained within their VLAN, too. To communicate across VLANs, you need an L3 router in between (inter-VLAN routing).
- Compared with VXLAN (Lab 18). Both isolate and multiplex L2, but VLAN divides a single link / a local L2 segment with a 12-bit VID, whereas VXLAN carries L2 across an L3 network and divides it with a 24-bit VNI (about 16 million segments). VXLAN emerged because data centers outgrew VLAN's 4094 IDs and needed L2 to span L3 boundaries.
The key insight: a single tag makes one wire look like multiple isolated LANs. You can partition networks without adding physical links.
Common Pitfalls
- Assuming VLANs can reach each other automatically. They are separate broadcast domains; crossing them requires L3 routing.
- Where the tag sits. Inside the Ethernet header (after the source MAC) — below IP.
- Trunk vs access. A trunk carries multiple VLANs with tags. An access port carries one VLAN, untagged (both ends in this lab behave like trunks).
- VID range. 12 bits = 1–4094. When that's not enough, VXLAN's 24-bit VNI is the escape hatch.
- The same subnet on different VLANs. IP subnets and VLANs are separate concepts, but the usual convention is one VLAN = one subnet.
- Capture filters.
tcpdumpcan filter withvlan 100; use-eto display the L2 header (including the tag).
Cleanup
sudo containerlab destroy -t vlan-26.clab.yml --cleanup
If you used labctl.sh run vlan-26, the script runs destroy for you at the end.
Check Your Understanding
- What does a VLAN separate? What does the 802.1Q tag contain?
- What is a trunk link? Which link in this lab is a trunk?
- Why can't VLAN 100 and VLAN 200 reach each other even though they share the same wire?
- What is required for communication between VLANs?
- Explain the difference between VLAN (802.1Q) and VXLAN (Lab 18) in terms of isolation scope and ID bit width.
- Does the tag live in the Ethernet header or the IP header?
References
- IEEE 802.1Q: Bridges and Bridged Networks (VLANs)
- RFC 5517: Cisco Systems' Private VLANs
- RFC 826: An Ethernet Address Resolution Protocol
- RFC 5737: IPv4 Address Blocks Reserved for Documentation
Verified Run Log (2026-07-07)
This lab has been confirmed reproducible on real hardware.
Environment:
- Ubuntu 26.04 LTS (kernel 7.0.0-27-generic, x86_64)
- Docker 29.1.3
- containerlab 0.77.0
node-a/node-b:nicolaka/netshoot:latest(tcpdump 4.99.6, with 802.1Q decoding)
Running PATH="/tmp/pl-shim:$PATH" ./scripts/labctl.sh run vlan-26 performed deploy → verify → destroy, and verification.json returned "status": "verified".
One eth1, two VLANs separated by tags
$ docker exec clab-vlan-26-node-a tcpdump -n -e -r vlan.pcap
aa:c1:ab:d5:69:7c > aa:c1:ab:cc:4a:85, ethertype 802.1Q (0x8100), length 102:
vlan 100, p 0, ethertype IPv4, 10.0.100.1 > 10.0.100.2: ICMP echo request
... ethertype 802.1Q (0x8100) ... vlan 200, ... 10.0.200.1 > 10.0.200.2: ICMP echo request
The trunk — a single eth1 — carries tagged frames for both vlan 100 and vlan 200 (ethertype 802.1Q 0x8100). They share one physical link, yet the tags split it into two LANs.
VLAN 200 never mixes into VLAN 100's traffic (isolation)
$ docker exec clab-vlan-26-node-a tcpdump -n -e -r v100-only.pcap # "vlan 100" filter
... vlan 100, ... 10.0.100.1 > 10.0.100.2: ICMP echo request/reply
(zero vlan 200 frames)
During the VLAN-100 ping, the wire shows only vlan 100 frames. Each VLAN is isolated as its own broadcast domain; bridging between them takes L3 routing. And where VXLAN (Lab 18) spans L3 with a 24-bit VNI, VLAN works on a single link with a 12-bit VID — the difference is visible right here.
Cleanup
containerlab destroy -t vlan-26.clab.yml --cleanup
That's 802.1Q in a nutshell: one small tag turns a single wire into multiple isolated LANs, no extra cabling required.
Explore the full Protocol Lab series here: github.com/pathvector-studio/protocol-lab. If these labs are useful to you, please ⭐ star the repo on GitHub — it genuinely helps others find the project.
Next up, we'll look at what it takes to route between these isolated networks — and where L2 separation stops being enough.