---
title: VLANs
source: 'https://academia.sh/en/courses/switching-and-routing/vlans'
course: 'Switching and Routing'
language: en
updated: '2026-08-17T18:07:19+00:00'
license: 'CC BY-SA 4.0'
---

# VLANs

Segmentation splits the broadcast domain but not the table row: on forty frames, flooding delivered drops from 280 to 51 and to 8, row count stays at 16, and the cost is a black hole of between 23 and 32.

The previous lesson counted flooding's cost and showed where that cost comes from: a
flooded frame goes to the entire broadcast domain, so its cost is multiplied directly by
that domain's size. In an eight-station domain, a single flood costs eight copies; the same
flood in an eighty-station domain costs eighty.

The way out this suggests is shrinking the domain. But the known way to shrink a domain is
to physically separate the network: separate switches, separate cables, separate locations.
This lesson's question is: can more than one broadcast domain be defined on the same
physical switch, without changing the cabling, and if so, **what is gained, what is paid?**

## The ID Is Assigned to the Port

A **VLAN** is a switch dividing its ports into numbered sets. Each port is assigned an
**ID**; the switch passes a frame arriving on one port only to ports carrying the same ID.
Flooding obeys the same rule: a frame with an unknown destination is flooded not to every
port, but only to the ports of its own ID.

That is the entire mechanism, and it changes nothing in the frame. The station is unaware
the ID exists; the frame it sends is an ordinary frame. The ID lives in the switch's
configuration, not in the frame. When a port's ID is changed, the station on it has been
moved to another broadcast domain, and it does not notice.

The direct consequence is that the broadcast-domain count is no longer tied to cabling. A
single eight-port switch can carry four broadcast domains; eight separate switches can
carry a single one. The domain's boundary is not physical, it is **declarative.**

## The Trunk Link

On a single switch, the ID is not written into the frame, because it does not need to be:
which ID a frame belongs to is known from the port it arrived on. When two switches sit
side by side, this information is lost. Which ID the frames sharing the single cable
between them belong to is undetermined for the other switch.

The fix is to write the ID into the frame. On a **trunk link**, the frame passes carrying a
four-byte tag inserted between the source address and the type field.

| Field | Width | Carries |
|---|---|---|
| Tag protocol identifier | 16 bits | A fixed value marking the frame as tagged |
| Priority | 3 bits | The frame's processing priority |
| Drop-eligible indicator | 1 bit | A notice that it can be dropped first under congestion |
| ID | 12 bits | The VLAN's number |

The ID field is 12 bits; because the smallest and largest values are reserved, the number
of usable IDs is 4094. The tag exists only on the trunk link: it is stripped off as the
frame goes out to a station, because the station is unaware of the tag.

```text
# taught transcript, not run

untagged frame (station <-> switch)
  destination | source | type | payload | check

tagged frame (switch <-> switch)
  destination | source | TAG | type | payload | check
                 ^ 4 bytes: id-mark + priority + drop-eligible + id

port configuration (switch 1)
  port 1  untagged, id 1
  port 2  untagged, id 1
  port 3  untagged, id 2
  port 4  untagged, id 2
  port 5  TAGGED,   ids carried: 1, 2
```

Which IDs a trunk link carries is a separate decision. If an ID has no members on the other
switch, carrying it buys nothing; when it is not carried, no frame belonging to that ID
crosses over, and the switch on the other side never sees that ID's addresses. The
measurement counts this decision's cost separately.

Assigning an ID to a port is not the only method. In port-based assignment, the decision is
tied to the cable: whatever the port is, that is the ID, no matter which station gets
plugged in. In address-based assignment, the switch looks at an incoming frame's source
address and reads the ID from a mapping table; if the station moves, its ID moves with it.
The second buys portability, and charges in return a second table the switch has to keep,
plus keeping that table current. The measurement uses port-based assignment, because what
is counted is not the method of assignment but segmentation itself.

## The Native VLAN and Disagreement Between the Two Ends

What to do when an untagged frame appears on a tagged link is decided separately. Every
trunk link has a **native VLAN**: an untagged incoming frame is counted as belonging to
that ID, and a frame leaving that ID is sent untagged. The domain is recognized not by
carrying an ID, but by not carrying one.

This opens a configuration surface that belongs to the course's axis. What the native VLAN
is gets written **separately** at the two ends of the link, and there is no synchronization
between them. If the two ends write the same number, there is no problem. If they write
different numbers, a frame leaving one end untagged is counted into a different domain at
the other end: the frame **crosses from one broadcast domain to another with no device
making that decision.** There is no sign of malfunction — the link is up, counters are
clean, frames are not dropped. The only visible thing is that two domains meant to be
separate are sharing some frames.

**Narrowing** takes three steps. First, the native VLAN is written to the same value at
both ends, and that value is treated as a reviewed part of the configuration. Second, the
native VLAN is pulled to a number assigned to no station; if that domain has no members,
there is no traffic for the mismatch to carry either. Third, the ID list a trunk link
carries is written in its narrowest form: IDs with members on both sides are carried, the
rest are not. The measurement's third regime is the extreme case of this narrow
configuration.

## The Measurement's Assumptions

- **ND15** — There are two switches, four stations connected to each, and a single trunk
  link stands between them. The oracle is known because we built the layout ourselves.
- **ND16** — Forty frames are drawn from a single generator with a single modulus; draws
  where the source equals the destination are discarded. The same frame sequence is used
  across all four segmentations.
- **ND17** — Four regimes are compared: unsegmented; two domains, both **spanning** the two
  switches; two domains, each **local** to a single switch; four domains, all spanning.
- **ND18** — The delivered column counts the **flooding** regime: as if the destination
  were not in the table, the frame is given to every station in its own domain. The goal is
  to isolate segmentation's effect on flooding; the table's own gain was measured in the
  previous lesson.
- **ND19** — The trunk link carries an ID that has members on both sides; it does not carry
  an ID without members. This is the narrowest form of the configuration.
- **ND20** — A table row is an `(id, address)` pair, and a switch writes only a source it
  has **seen.** Whether the other switch sees a given source depends on the trunk link
  carrying that ID.
- **ND21** — A frame whose source and destination are in separate IDs is counted a **black
  hole**: the switch floods it within its own domain, and since the destination is not in
  that domain, no copy reaches it. The set's resolution is forty frames, the smallest
  measurable difference is $1/40 = 0{,}025$.

## The Measurement

```python
"""Segmentation's effect on the broadcast domain, table row, and trunk link."""
SEED = 20260810
STATIONS = ["a", "b", "c", "d", "e", "f", "g", "h"]
SWITCH = {"a": 1, "b": 1, "c": 1, "d": 1, "e": 2, "f": 2, "g": 2, "h": 2}
SEGMENTATION = {
    "unsegmented": {a: 1 for a in STATIONS},
    "two domains, spanning": {"a": 1, "b": 1, "c": 2, "d": 2,
                              "e": 1, "f": 1, "g": 2, "h": 2},
    "two domains, local": {"a": 1, "b": 1, "c": 1, "d": 1,
                           "e": 2, "f": 2, "g": 2, "h": 2},
    "four domains, spanning": {"a": 1, "b": 2, "c": 3, "d": 4,
                               "e": 1, "f": 2, "g": 3, "h": 4},
}


def generator(seed):
    d = seed % 2147483646 + 1

    def r(n):
        nonlocal d
        d = (d * 48271) % 2147483647
        return d % n
    return r


def frames(count=40):
    r, result = generator(SEED), []
    while len(result) < count:
        x, y = STATIONS[r(8)], STATIONS[r(8)]
        if x != y:
            result.append({"source": x, "destination": y})
    return result


def carried_ids(id_map):
    """The trunk link carries an ID that has members on both sides."""
    return {k for k in set(id_map.values())
            if {SWITCH[s] for s in STATIONS if id_map[s] == k} == {1, 2}}


def measure(C, id_map):
    carried = carried_ids(id_map)
    rows = {1: set(), 2: set()}
    fate = {"reached": 0, "black hole": 0}
    delivered = unnecessary = trunked = 0
    for c in C:
        k, x, y = id_map[c["source"]], c["source"], c["destination"]
        rows[SWITCH[x]].add((k, x))
        if k in carried:
            rows[3 - SWITCH[x]].add((k, x))
        if id_map[y] != k:
            fate["black hole"] += 1
            continue
        outputs = [s for s in STATIONS if id_map[s] == k and s != x]
        delivered += len(outputs)
        unnecessary += sum(1 for s in outputs if s != y)
        trunked += 1 if SWITCH[y] != SWITCH[x] else 0
        fate["reached"] += 1
    return fate, delivered, unnecessary, trunked, len(rows[1]) + len(rows[2])


C = frames()
print(f"frames {len(C)} | stations {len(STATIONS)} | switches 2")
print()
print(f"{'segmentation':<23s} {'domains':>7s} {'reached':>7s} {'black hole':>10s} "
      f"{'delivered':>9s} {'unnecessary':>11s} {'trunked':>7s} {'rows':>5s}")
for name, id_map in SEGMENTATION.items():
    fate, d, u, t, r = measure(C, id_map)
    print(f"{name:<23s} {len(set(id_map.values())):7d} {fate['reached']:7d} "
          f"{fate['black hole']:10d} {d:9d} {u:11d} {t:7d} {r:5d}")
```

```
frames 40 | stations 8 | switches 2

segmentation            domains reached black hole delivered unnecessary trunked  rows
unsegmented                   1      40          0       280         240      29    16
two domains, spanning         2      17         23        51          34      14    16
two domains, local            2      11         29        33          22       0     8
four domains, spanning        4       8         32         8           0       8    16
```

## The Domain Splits, the Row Does Not

The delivered column keeps segmentation's promise. Flooding forty frames on the
unsegmented network produces **280** deliveries; split into two domains it drops to **51**,
into four domains to **8**. Doubling the domain count drops the cost to roughly a quarter,
because both the number of frames flooded and the number of stations each flood reaches
shrink together.

In the four-domain regime, unnecessary copies drop to **0**, and the reason is interesting.
Every domain in that regime has two stations; the frame a station floods within its own
domain goes to the domain's only other station — exactly the destination. **Once a domain
shrinks to two stations, flooding and learned forwarding are the same thing.** The table
buys no gain there; segmentation has done the table's job.

The rows column, however, does not keep the promise. In three of the four regimes, the row
count is **16**: even though segmentation splits the broadcast domain, it does not split the
table row. The reason is direct — the table separates addresses by ID, but does not
**destroy** them. Eight addresses split into four domains are still eight addresses; every
switch still sees all eight, because the trunk link carries every ID and every source
address crosses over.

The two two-domain regimes giving different results comes from the same distinction. The
domain count is two in both, but reached frames are **17** in one, **11** in the other. The
difference comes not from segmentation's coarseness but from its **placement**: when
domains span the two switches, a domain's members are four stations; when domains stay
local to a single switch, it is also four, but this time who talks to whom is tied to the
cable. Same domain count, same member count, different result.

What actually splits the row is not segmentation but **what the trunk link carries.** In
the third regime, every domain stays local to a single switch; the trunk link has no shared
ID left to carry, and each switch learns only its own four addresses. Rows drop from **16**
to **8**. This is not a result of segmentation alone, but of **segmentation together with
placement.**

## The Black Hole Segmentation Produces

The gain's cost sits in the black-hole column, and what is paid is not small.

On the unsegmented network, all forty of the forty frames reach their destination. Split
into two domains, **23** frames fall into a black hole; in local segmentation, **29**; in
four domains, **32**. In a set of forty, this last value is **0.800**: segmentation has
stopped more than three-quarters of the traffic.

This is not a malfunction, it is the definition itself. Splitting the broadcast domain
means those domains cannot send frames to each other; the silence gained and the access
lost are two faces of the same thing. The measurement writes this under a fate name because
the frame genuinely dies: the switch floods it within its own domain, and since the
destination is not in that domain, no copy reaches it and no one reports an error.

Where the distinction has shifted deserves attention. In the previous lesson, a black hole
was a **misjudgment** — the table was stale, the device went out the wrong port. Here, a
black hole is a **decision**: the table is correct, the ID is correct, the device has done
exactly what was asked of it. The same name is written for two separate reasons, and what
separates them is whether the table is right.

This second kind of black hole also decides where the domain's boundary falls. A broadcast
domain is the largest set within which address resolution can walk: a station can only ask
about an address in its own domain, because its question travels by a broadcast frame, and
that frame does not leave the domain. This is why the boundary of the broadcast domain and
the boundary of the address block land in the same place; the subnet arithmetic established
in the Network Models and Protocols course is not repeated here, but segmentation requires
a separate block for every new domain. In a set of forty frames, **32** black holes is
**0.800** and thirty-two times the resolution; at this scale, the claim rests on the
definition of segmentation, not on the fiction's detail.

The trunked column gives a third reading. On the unsegmented network, **29** frames use the
cable between the two switches; split into two domains it drops to **14**, in local
segmentation to **0**. Local segmentation empties the cable entirely — but the cable it
empties is at the same time the reason **29** frames die. A link going unused does not show
that it is unnecessary.

## Summary

- A VLAN assigns an ID to a port; the station does not see the ID, and no field is added to
  its frame. The broadcast domain's boundary stops being physical and becomes declarative.
- A trunk link writes the ID into the frame with a four-byte field; a 12-bit ID field gives
  4094 usable values, and the tag is dropped as the frame goes out to a station.
- Segmentation brings flooding delivered down from **280** to **51** and **8**; once a
  domain shrinks to two stations, unnecessary copies become **0** and flooding turns into
  the same thing as learned forwarding.
- The table row stays at **16** in three of the regimes: segmentation separates addresses,
  it does not destroy them. What brings the row down to **8** is not segmentation, but the
  trunk link never carrying that ID at all.
- The cost is the black hole: **0**, **23**, **29**, **32**. This black hole is not a
  misjudgment but a decision — the table is correct and the device has done what was asked.

## Next Step

In this measurement, there was a single cable between the two switches, and every flooded
frame crossed it once and was done. If a second cable is run between the two switches, the
situation changes: a flooded frame goes out the first cable and comes back on the second,
and once it comes back, it gets flooded again, this time going out the first cable. There
is no domain to stop the frame — the link-layer frame carries no hop count. The next lesson
measures this consequence of redundant links and the decision made by the protocol that
prevents it: which link gets blocked, and what happens to a packet's fate during the round
before the block is recomputed.
