Packets, Paths & Labels
A networking guide · from the WAN down to the kernel

Packets, paths
and labels

No router knows the whole internet. Every packet you send is handed between networks that don’t trust each other — and at the far end, between two stacks on one machine joined by nothing but a pointer. This guide works downward through both.

distance vector link state path vector labels · tags · overlays the host kernel
New to all this Start with the four that stand alone, then fill in around them.
You run a cluster The tenancy thread, from the fabric down to the policy engine.
You’re buying hardware What the money goes on, and which numbers are load-bearing.
The whole thing Open Origins and follow Next. It is built to be read downward.
Origins

Who built this, and what for

Why does networking look the way it does?

Almost nothing here was designed. Each piece was added by someone with a specific problem, on top of whatever was already deployed — which is why the result layers the way it does.

1956Bellman–Ford · DijkstraBellman, Ford, Moore · DijkstraTwo shortest-path algorithms, published before there was a network to run them on. Everything in Part II is packaging around these.
1982EGPEric Rosen, BBN · RFC 827Split the ARPANET into a core plus stub networks. It assumed a single core arranged as a tree — and the internet promptly stopped being either.
1983RIPXerox PARC, via Berkeley UnixShipped as routed in 4.2BSD and was on every machine before anyone wrote it down. RFC 1058 documented it five years later rather than specifying it.
1985Spanning treeRadia Perlman, DECLet Ethernet bridges have redundant links without melting down. The price is that the redundant link is blocked rather than used — the grievance that Part III eventually settles.
1987IS-ISRadia Perlman, DEC · ISO 10589Link state for OSI’s CLNP, not for IP. That accident is why it still runs straight on layer 2, and why it absorbed IPv6 and segment routing without a new version.
1989OSPFIETF, John Moy · RFC 1131The IETF wanted a link-state IGP that was IP-native and open, instead of adopting ISO’s. Both survived; the industry still runs one in enterprises and the other in carriers.
1989BGPKirk Lougheed, Cisco · Yakov Rekhter, IBMSketched on napkins at an IETF lunch. EGP’s one-core tree could not describe a commercial internet where networks multi-home and choose who to carry.
1993EIGRPCisco, on Garcia-Luna-Aceves’s DUALDistance vector without the counting: keep a provably loop-free backup so failover needs no conversation at all. Proprietary until Cisco published it in 2016.
1996Tag switching → MPLSCisco, Ipsilon, IBM · IETF WG 1997Built to forward at ATM speed without an IP lookup per hop. That reason expired within a decade; the labels stayed because they could mean customer and engineered path.
1998802.1Q VLANIEEE 802.1One switch serving many broadcast domains, standardised after years of incompatible vendor trunking. Twelve bits looked like plenty at the time.
1999MPLS L3VPNRosen & Rekhter · RFC 2547Carriers wanted to sell a private IP network over a shared core without running a separate router — or a separate table — per customer.
2001LDPIETF · RFC 3036A label for every IGP prefix, following the IGP’s own shortest path. No engineering, no configuration — and one more session per link to keep in sync.
2008vethPavel Emelyanov · Linux 2.6.24Network namespaces had arrived and there was no way to plug one into anything. The answer was two devices that are each other’s transmit path.
2010BFDDave Katz & Dave Ward · RFC 5880Every protocol’s own hello timer was too slow and none of them could be sped up safely. One heartbeat underneath all of them instead.
2014VXLANVMware, Cisco, Arista et al. · RFC 73484094 VLANs is not a cloud. Wrapping the frame in UDP made a segment stop caring about cabling — and made the fabric a routed one.
2015EVPNSajassi et al. · RFC 7432Stop flooding in order to learn MAC addresses. Use BGP, which had spent twenty-five years learning how to distribute reachability.
2018Segment routingClarence Filsfils et al., Cisco · RFC 8402Delete LDP and RSVP-TE outright. Put the labels in the IGP and the path in the packet, so the core holds no per-flow state.
The pattern worth noticing: nothing was ever replaced. RIP is still in the tree because it shipped with Unix; IS-IS runs the carrier core because it was built for a protocol nobody uses; BGP does jobs its name forbids. Each layer in this guide is a workaround for the layer beneath it that turned out to be permanent.
Part I
Between networks

Two administrations, no shared trust, and a boundary that gets negotiated rather than measured — plus the label-switched core that carries the result.

01 — The shape of it

Two problems, not one

Why isn’t there just one routing protocol?

Inside a network, routing is an engineering question: which way is fastest? Between networks, it is a business question: whose traffic am I willing to carry?

AS 64500 · campus AS 64510 · transit carrier AS 64520 · cloud IGP · OSPF IGP · IS-IS IGP · OSPF eBGP eBGP IGP adjacency — one owner, full trust, metric wins eBGP session — two owners, no trust, policy wins
An autonomous system is one routing domain under one administration — a campus, a carrier, a cloud region. Inside, an interior gateway protocol (IGP) finds fast paths. At the edges, an exterior gateway protocol (EGP) — in practice only BGP — negotiates which prefixes cross the boundary at all.
Interior · IGP

Optimise

  • Scope: one AS, tens to thousands of routers.
  • Goal: the cheapest path by a metric — hops, bandwidth, delay.
  • Assumes every router is honest and configured by the same team.
  • Convergence in milliseconds to seconds; a flap is a local event.
  • RIP, EIGRP, OSPF, IS-IS.
Exterior · EGP

Negotiate

  • Scope: between ASes — ~75,000 of them, ~1M prefixes.
  • Goal: the path your contracts and policy allow. Shortest is a tiebreak.
  • Assumes the neighbour may be wrong, hostile, or leaking.
  • Convergence in seconds to minutes; a flap is everyone’s event.
  • BGP-4. There is no second option.
02 — The family tree

Three ways to answer “which way?”

What are all these protocols, and how do they differ?

Every routing protocol differs on two axes: who you talk to, and what you tell them.

Routing protocols Interior — inside one AS Exterior — between ASes Distance vector RIP · EIGRP Link state OSPF · IS-IS Path vector BGP-4
“Gateway protocol” is about where a protocol runs; “distance vector / link state / path vector” is about how it computes. EIGRP is a distance vector protocol with the reflexes of a link state one; BGP is a distance vector protocol that carries the whole path instead of just a number.
03 — BGP

BGP: a distance vector protocol that shows its work

How do two networks that don’t trust each other agree on a route?

Carrying the full AS path solves the loop problem outright — and doubles as the raw material for policy. Here is what actually moves between two speakers.

UPDATE · 203.0.113.0/24 — every eBGP hop prepends its own AS number and rewrites the next hop AS_PATH 15169 NEXT_HOP 198.51.100.1 AS_PATH 3356 15169 NEXT_HOP 198.51.100.5 AS_PATH 64510 3356 15169 NEXT_HOP 198.51.100.9 fed back to 3356 — its own AS is already in the path, so it is discarded unread AS 15169 origin AS 3356 transit AS 64510 transit AS 64500 you
The prefix never changes; the path attributes do. Each AS prepends itself, so the path is both a loop check and a distance metric — and because it is a list of who rather than a number, it is also the thing policy is written against. NEXT_HOP is rewritten at every eBGP hop, which is why your IGP has to know how to reach it.
per peer per peer Adj-RIB-In peer A · untouched Adj-RIB-In peer B · untouched import policy drop, tag, set LOCAL_PREF best path the seven tiebreaks Loc-RIB one winner per prefix export policy prepend, filter, set MED Adj-RIB-Out peer C · what I will say handed to zebra, which weighs it against every other protocol at AD 20 or 200, then programs the kernel FIB and the ASIC Only the winner is ever advertised. A BGP speaker tells its neighbours its conclusion, never its options — so a network’s alternative paths are invisible from outside it.
Three tables, not one. Adj-RIB-In keeps what each peer actually said, so a policy change can be re-applied without asking again. Loc-RIB holds one winner per prefix. Adj-RIB-Out is what this speaker will say to each peer, after export policy has had its turn — which is why the same prefix can leave with a different AS_PATH length for every neighbour.
peer A bgpdAdj-RIB-In · Loc-RIB · Adj-RIB-Out ospfd / isisdlearns every loopback zebraone winner per prefix, by admin distance kernel FIB ASIC · SAIwhat actually forwards peer B bgpdAdj-RIB-In · Loc-RIB · Adj-RIB-Out ospfd / isisdlearns every loopback zebraone winner per prefix, by admin distance kernel FIB ASIC · SAIwhat actually forwards TCP 179 · the session UPDATE the wire · IGP adjacency, no TCP peers on loopbacks — the adjacency is what makes them reachable 4 2 5 3 1 session state · Established
1The adjacency comes up. OSPF or IS-IS hellos on the wire — no TCP, and the peer was never configured. Each router learns the other’s loopback.
2zebra installs it. The loopback route goes into the kernel FIB, so that address becomes reachable.
3bgpd opens the session. A TCP connection to that loopback on port 179. It could not have existed before step 1 — this is why BGP needs an IGP.
4UPDATEs arrive. bgpd applies import policy, runs the seven tiebreaks, and offers one winner per prefix.
5zebra arbitrates. Administrative distance decides between bgpd’s candidate and ospfd’s, and the winner is programmed into the FIB, then the ASIC.
Two processes, one TCP connection, and a dependency that explains the whole arrangement: a BGP session is built loopback to loopback, and only the IGP knows how to reach a loopback. Step it and the point is unavoidable — the session cannot be drawn until the adjacency below it exists.
complete
Why path vector
  • Each advertisement carries AS_PATH, e.g. 64500 3356 15169.
  • A router that sees its own AS number in the path drops the route. Loops die at the border, with no counting.
  • The path is also evidence: you can filter on who it crossed, prepend your own AS to look worse, or refuse it entirely.
  • Runs over TCP 179. Sessions are configured by hand, never discovered — and each one is a separate deliberate relationship.
Best-path order
  • 1 — highest LOCAL_PREF (what I want)
  • 2 — shortest AS_PATH
  • 3 — lowest ORIGIN
  • 4 — lowest MED (what they asked for)
  • 5 — eBGP beats iBGP
  • 6 — lowest IGP cost to the next hop (hot potato)
  • 7 — oldest route, then lowest router ID

Exterior · eBGP

Between two autonomous systems. This is the boundary BGP was designed around, and the only place its caution makes sense.

What it does to a route
  • Prepends its own ASN to AS_PATH — which is simultaneously the loop check, the distance metric, and the evidence policy is written against.
  • Rewrites NEXT_HOP to itself, so the far side is given an address it can actually reach.
  • Re-advertises what export policy allows, to the peers export policy allows. Nothing is passed on by default.
  • Decrements nothing else. The prefix itself is never altered — only the attributes travelling with it.
Why it is never discovered
  • Every peer is configured by hand, by address and ASN. There is no hello, no multicast, nothing to stumble into.
  • Directly connected by default — TTL 1. Reaching a peer further away needs ebgp-multihop, stated deliberately.
  • That is the point: a session is a commercial relationship. You agreed to carry this traffic, so you name who you agreed with.
  • Datacentre fabrics soften the addressing — dynamic neighbours, BGP unnumbered over IPv6 link-local — but never the trust. You still declare the expected ASN.

Interior · iBGP

The same protocol between two routers inside one AS. Almost everything that looks strange about BGP network design comes from this half.

What changes
  • Does not prepend. AS_PATH is unchanged inside the AS — which is exactly why the next card exists.
  • Does not touch NEXT_HOP. It stays the far eBGP neighbour’s address, so your IGP has to be able to reach it — or you configure next-hop-self and take the job on yourself.
  • Peers loopback to loopback, any number of IGP hops apart. The session has no idea how many routers it crossed.
The rule that shapes every network
  • A route learned from one iBGP peer is never re-advertised to another. Not policy — protocol.
  • Because AS_PATH does not grow internally, there is no loop detection available. The prohibition has to be structural instead.
  • So every speaker must hear it directly: a full mesh, n(n−1)/2 sessions — 4,950 at a hundred routers. Route reflectors lift the rule and substitute ORIGINATOR_ID and CLUSTER_LIST as the loop check; confederations fake sub-ASes instead.
  • And note what iBGP is not: an IGP. It carries no topology, computes no paths, and its sessions exist only because an IGP made those loopbacks reachable — which is precisely what the walkthrough above is showing.
Four messages, and that is all
  • OPEN — my AS, my router ID, my hold time, my capabilities: 4-byte ASNs, which address families I speak, route refresh, add-path. A capability the other side rejects means no session at all.
  • UPDATE — withdrawn prefixes, one set of path attributes, then every prefix sharing that set. Packing many NLRIs behind one attribute block is why a million-route table transfers in far fewer than a million messages.
  • KEEPALIVE — every 60 s, a third of the hold timer. Carries nothing.
  • NOTIFICATION — always fatal. An error code, then the session dies and every route learned over it is withdrawn at once.
  • The state machine is Idle → Connect → Active → OpenSent → OpenConfirm → Established. A session stuck in Active is not active; it is failing to connect and retrying — the worst-named state in networking.
Read the best-path order again: path length is only step two, and it is measured in networks crossed, not milliseconds. A route through four small ASes can beat one through two huge ones. BGP was never trying to be fast — it was trying to be agreeable.
04 — MPLS

Stop asking where, start asking which

Why forward on a label instead of an address?

MPLS puts a short fixed label in front of the packet. Every router after the first one matches that label exactly, and never looks at the IP address again.

IP in IP out push 17 swap 17 → 22 pop — penultimate hop 17 IP 22 IP IP LER LSR LSR LER ingress edge core penultimate egress edge classifies into a FEC label lookup only — no IP lookup, no routing table consulted normal IP forwarding
The ingress LER decides once which forwarding equivalence class (FEC) a packet belongs to and pushes a label. Core LSRs only swap labels. The second-to-last router pops it — penultimate hop popping — so the egress router isn’t asked to do two lookups.
Label20 bits
TC3 bits · QoS
S1 bit · bottom
TTL8 bits

That is the whole MPLS header: 32 bits, shimmed between the layer-2 frame and the IP packet — which is why MPLS is called “layer 2.5”. The S bit marks the bottom of the stack, because labels stack: an outer one for the tunnel across the core, an inner one identifying the customer at the far end.

What a label buys you
  • L3VPN. The inner label names a customer VRF, so two customers can both use 10.0.0.0/8 across one core that never learns either.
  • Traffic engineering. A path can be pinned away from the shortest route — something no IGP metric can express.
  • Fast reroute. A pre-computed backup tunnel swings traffic in ~50 ms, before any protocol has reconverged.
  • Any payload. Ethernet, ATM, IPv6 over an IPv4 core — the core only reads labels.
Who hands out the labels
  • LDP — follows the IGP’s shortest path. Simple, no engineering.
  • RSVP-TE — signals an explicit path with bandwidth reserved. Powerful, and state-heavy in the core.
  • Segment routing (SR-MPLS) — the modern answer: the IGP itself distributes labels, the ingress stacks them to describe the path, and the core holds no per-flow state.
  • MPLS never finds paths itself. It always rides an IGP — usually IS-IS or OSPF — and BGP for the VPN routes.
The common myth: that MPLS exists because IP lookups were slow. That was true in 1997; today silicon does longest-prefix match at line rate. MPLS survived because a label can mean something an IP address cannot — this customer, this engineered path, this backup tunnel.
05 — Multi-tenancy

One address, many tenants

How can two customers both use 10.0.0.0/8 on one network?

Two customers both use 10.0.0.0/8 and both cross the same core. Nothing collides, because in the core the packet is never routed by its address — the label carries the tenant.

MP-BGP session · VPNv4 65000:1:10.0.0.0/8 → label 31 · RT 65000:1 65000:2:10.0.0.0/8 → label 44 · RT 65000:2 22 31 IP outer 22 — how to reach PE2 · from LDP or SR inner 31 — which VRF once there · from MP-BGP VRF blue 10.0.0.0/8 VRF red 10.0.0.0/8 VRF blue 10.0.0.0/8 VRF red 10.0.0.0/8 P PE1 — the only router that knows the tenants holds no tenant routes PE2 — pops 31, looks up in blue’s table
The outer label is popped one hop early, so PE2 receives the packet with only label 31 on top. That label — not the destination address — is what selects blue’s forwarding table. The same packet with label 44 would land in red’s.
At the edge · VRF
  • The PE keeps a separate RIB and FIB per tenant. Customer interfaces are bound to one VRF.
  • Blue’s 10.0.0.0/8 and red’s are rows in two different tables that never meet — no more magic than two network namespaces on one host — and Linux has the VRF abstraction itself, in section 17.
  • P routers in the core hold zero tenant routes, which is the whole scaling argument.
In the control plane · RD and RT
  • Both prefixes still have to cross one shared MP-BGP mesh, where identical NLRIs would collide.
  • An 8-byte route distinguisher is prepended to make a unique VPNv4 prefix: 65000:1:10.0.0.0/8. Purely a uniquifier — it carries no policy.
  • Route targets are the separate thing: communities attached on export, matched on import, deciding which VRFs a route lands in. RD = uniqueness. RT = membership, and therefore hub-and-spoke, extranet, shared services.
On the wire · two labels
  • Outer — the transport label, from LDP or SR, answering “how do I reach PE2”. It is all the core ever reads.
  • Inner — the VPN label, allocated by PE2 and advertised with the route, answering “whose table do I use when I get there”.
  • No destination-IP lookup happens anywhere in the shared domain, which is exactly why the overlap is harmless.
The catch: this buys isolation, not interoperability. Blue’s 10.1.1.1 and red’s still cannot talk to each other — joining them needs NAT or a shared-services VRF with non-overlapping addressing. Overlap is only free while the two tenants stay strangers. The same shape turns up in VXLAN/EVPN, where the VNI plays the VPN label’s part and the RD/RT model is borrowed wholesale: the tenant identifier has to travel with the packet, because the address stopped being unique.
06 — Sessions all the way down

Four conversations, each useless without the one below

What has to come up, and in what order, before anything works?

Everything in this part runs at once, on the same routers, over the same links. What makes it work is the order — and what makes it fail is that the order is never enforced.

each layer needs the one below between the two ends only — and only if you need a pseudowire targeted LDP VPNv4 routes + VPN labels · TCP 179 · loopback to loopback MP-BGP label bindings · TCP 646 label bindings · TCP 646 label bindings · TCP 646 LDP adjacency · hello 10 s adjacency · hello 10 s adjacency · hello 10 s IGP 3 × 50 ms 3 × 50 ms 3 × 50 ms BFD PE1 edge P1 no BGP P2 no BGP PE2 edge
Bar length is who the conversation is with. BFD, the IGP and LDP are per link — one of each between every adjacent pair. MP-BGP is end to end, PE to PE, passing straight over the core routers, which is why a P router never learns a tenant route. Turn on segment routing and the LDP band disappears: the IGP carries the labels itself.

A pedantic but load-bearing distinction: OSPF and IS-IS form adjacencies, not sessions. There is no TCP — OSPF rides IP protocol 89 with its own acknowledgement scheme, IS-IS sits straight on layer 2 — and they discover each other with multicast hellos. LDP and BGP have real TCP sessions, on ports 646 and 179, terminated on loopbacks. That difference is why an IGP adjacency survives things a session does not, and why a session can be down while the adjacency beside it looks perfect.

01

The link, then BFD

Interface up. BFD starts a bare heartbeat — typically 3 × 50 ms — that every protocol above subscribes to instead of running its own slow timers. It carries no routing information at all; its entire job is to say down in 150 ms rather than 40 seconds.

02

IGP adjacency

Hellos, database sync, SPF. The important output is not the transit routes — it is that every loopback in the network is now reachable, because every session above is going to be built between loopbacks.

03

LDP session

Discovery is per link — hellos to 224.0.0.2 on UDP 646. The session itself is TCP 646 between the two loopbacks, opened by the higher LSR-ID. Both ends then advertise a label for every prefix in the IGP, unsolicited, whether or not they are the next hop for it.

04

MP-BGP session

PE to PE, or PE to route reflector, TCP 179 loopback to loopback — a session that exists only because step 02 made those loopbacks reachable. VPN routes arrive carrying their VPN labels, and each one’s BGP next hop resolves to an LSP that step 03 already built.

LDP, concretely
  • Downstream unsolicited with liberal retention: every LSR hands out a label for every IGP prefix to every peer, and keeps every label it is given — including ones it cannot currently use.
  • That is deliberate waste. When the IGP moves the next hop, the label for the new path is already in the table, so the LSP follows in milliseconds.
  • Keepalive 60 s, hold 180 s — the same rhythm as BGP, and the same three-minute hole without BFD.
  • The LSR-ID is a loopback, so mpls ldp router-id Loopback0 force is not a formality: an unreachable loopback breaks LDP while every interface and adjacency still looks perfectly healthy.
When the order slips
  • LDP–IGP sync (RFC 5443) is the classic. A restored link converges in the IGP in milliseconds and starts attracting traffic — but LDP takes seconds to bring up its session and exchange bindings, so the packets arrive somewhere with no label and are dropped. A repaired link that black-holes traffic.
  • The fix is to make the IGP lie: advertise the link at max metric until LDP signals that it is ready, then drop to the real cost.
  • Failures cascade downward-up: one flapping link → IGP loses a loopback → LDP session drops → LSP gone → every VPN riding it black-holes. Four layers of consequence from one interface.
What SR deletes
  • Segment routing distributes labels inside the IGP itself — a prefix-SID is just another TLV in an IS-IS LSP or an OSPF opaque LSA.
  • So there is no LDP. No second session per link, no second set of timers, no second thing to get out of sync with the first.
  • The whole class of bug that RFC 5443 patches simply cannot occur: if the IGP has converged, the labels have converged, because they arrived in the same update.
  • RSVP-TE goes too, along with its per-LSP soft state refreshed every 30 s in every core router.
Count them on one PE in a modest network: four IGP adjacencies, four LDP sessions, four BFD sessions, two iBGP sessions to route reflectors — fourteen conversations, each with its own timers, its own liveness detection and its own opinion about whether the neighbour is up. iBGP is why the reflectors exist: a full mesh of n PEs needs n(n−1)/2 sessions, which is 4,950 at a hundred PEs. And every one of these is a session you can forget to configure on exactly one side.
Part II
Inside one network

One owner, full trust, and a free hand to optimise. This is the only place a metric means what it says.

07 — The two interior styles

Tell your neighbours everything, or tell everyone one thing

How does a router learn about a network it cannot see?

This single choice decides the memory a router needs, how fast the network heals, and how badly it fails.

DISTANCE VECTOR Talk only to neighbours — but hand them your conclusions. X Y Z “10.0.0.0/8 → cost 4, via me” …and 300 more rows, every 30 s Y never sees Z’s neighbours. It trusts Z’s arithmetic. LINK STATE Talk to everyone — but only about what you can see. S LSA: “my links, my costs” Every router ends up holding the same database, then computes alone.
The same network, two philosophies. Distance vector floods conclusions one hop at a time; link state floods observations everywhere and lets each router draw its own conclusion.
DISTANCE VECTOR cost to E, as each router believes it A ? B ? C ? D ? E ? D is directly connected to E, so it already knows. Nobody else has heard of E at all. elapsed 0 s LINK STATE LSAs held, out of the five in the network A ? B ? C ? D ? E ? Every router originates one LSA describing its own links. So far each holds only its own. elapsed 0 ms
Five routers in a line, every link costing 1. Same topology, same four rounds, same number of hops — information cannot outrun the wire in either scheme. What differs is the clock, and what each router is left holding. RIP's timer is the worst case; triggered updates shorten it, but the hop-by-hop crawl remains.
Round 0 of 4
Distance vector · Bellman–Ford
  • Knows: a number and a next hop per destination. Nothing about the topology.
  • Sends: its entire table, only to direct neighbours, on a timer.
  • Computes: my cost = my link + their claim. Keep the smallest.
  • Fails as: count to infinity — two routers quote each other’s stale route upward, one hop per round, while packets loop.
  • Patched by: split horizon, poison reverse, triggered updates, and a low ceiling (RIP: 16 = unreachable).
  • Costs: almost no CPU or memory. Cheap routers, slow truth.
Link state · Dijkstra
  • Knows: the full graph — every router, link and cost in its area.
  • Sends: one LSA describing only its own links, flooded reliably to all.
  • Computes: shortest-path tree over the database, rooted at itself.
  • Fails as: flooding storms and SPF churn when a flapping link keeps re-triggering every router at once.
  • Patched by: areas (OSPF) and levels (IS-IS) that cut the graph into summarised pieces.
  • Costs: real CPU and memory. Expensive routers, fast truth.
 Distance vectorLink statePath vector
What you storeone row per destinationthe whole topology graphevery path you were offered
Who you telldirect neighbours onlyevery router in the areaconfigured peers only
What you tellyour whole tableyour own linksprefix + full AS path + attributes
Whenperiodic, e.g. 30 son change, plus refreshon change, incremental
Loop defencehop ceiling and hacksimpossible — everyone shares one mapsee your own AS number, discard
Picks bymetricmetricpolicy, then metric
Scales totens of routershundreds per areathe entire internet
08 — Bookkeeping

Every protocol keeps its own books

Where do routes live before they reach the routing table?

A protocol’s database is not the routing table. Each one maintains a private view, argues with its peers inside that view, and submits a single candidate per prefix to the router it happens to be running on.

CONTROL PLANE — deciding DATA PLANE — doing RIPv2 one flat table + expiry timers EIGRP neighbour + topology + routing AD 120 AD 90 OSPF · IS-IS link-state database + SPF tree AD 110 / 115 BGP Adj-RIB-In → policy → Loc-RIB AD 20 · 200 RIB · routing table one winner per prefix install FIB · forwarding table compiled into hardware every packet, at line rate, consulting no protocol at all
Four protocols can be running at once, each holding a different amount of the truth. They never compare metrics with each other — administrative distance decides whose candidate gets installed, and only the RIB’s winners are ever compiled into the FIB.

RIPv2

distance vector
KeepsOne flat table: destination, metric, next hop, three timers. No neighbour list, no topology.
Finds neighboursIt doesn’t. Anything multicasting to 224.0.0.9 is believed.
On the wireIts entire table, 25 routes per datagram, UDP 520.
CadenceEvery 30 s whether anything changed or not, plus a triggered update on change.
Stays fresh bySilence. A route expires after 180 s unheard and is flushed at 240 s — absence is the only failure signal.

EIGRP

distance vector
KeepsThree tables: neighbours, topology (every path heard, with its reported distance), and the routing table (successors only).
Finds neighboursHello every 5 s, hold 15 s, on 224.0.0.10 / IP proto 88.
On the wireOnly what changed, only to the routers affected, over its own reliable transport with sequence numbers and acks.
CadenceNever periodic. Silence means nothing has changed.
Stays fresh byDUAL. A neighbour whose reported distance is lower than my current cost is provably not looping back through me — that feasible successor takes over instantly. With none, the route goes ACTIVE and the router must query its neighbours and wait.

OSPF

link state
KeepsThe LSDB — every LSA in the area, byte-identical on every router — plus a neighbour table and the SPF tree it derives from it.
Finds neighboursHello every 10 s, dead at 40 s. The adjacency climbs Down → Init → 2-Way → ExStart → Exchange → Loading → Full, and only then is the database trusted.
On the wireLSAs, flooded hop by hop and acknowledged. On a LAN a DR and BDR are elected so n routers form 2n adjacencies instead of n².
CadenceOn change — plus each LSA is re-originated every 30 min and expires at 60 min.
Stays fresh bySequence number, age and checksum on every LSA: the newer copy always wins, and the flood stops when a router already has it. SPF then re-runs over the database.

IS-IS

link state
KeepsOne LSDB per level — L1 inside an area, L2 for the backbone — and the same Dijkstra on top of each.
Finds neighboursIIH hellos every 10 s. A DIS is elected on a LAN with no backup: election is preemptive and simply re-runs.
On the wireLSPs built from TLVs — which is why IS-IS absorbed IPv6 and segment routing without a new protocol version.
CadenceOn change, with a 15-min refresh and a 20-min lifetime.
Stays fresh byThe DIS multicasts a CSNP — a complete index of the database — every 10 s. A router that spots a gap asks for the missing LSP with a PSNP. Continuous summary instead of per-update bookkeeping.

BGP-4

path vector
KeepsThree RIBs: Adj-RIB-In per peer (what they said, untouched), Loc-RIB (what I believe after import policy and best-path), Adj-RIB-Out per peer (what I’ll say back after export policy).
Finds neighboursNever. Every peer is configured by hand, by address and AS number, and the session is a plain TCP connection to port 179.
On the wireOne full table dump when the session comes up — then only UPDATEs, each carrying new prefixes and explicit withdrawals.
CadenceIncremental, forever. Keepalive every 60 s, hold timer 180 s.
Stays fresh byThe TCP session itself. Routes live exactly as long as the session does; when it drops, everything learned over it is withdrawn at once. To re-apply changed policy you ask the peer for a Route Refresh — there is no periodic re-send to wait for.
The pattern: the older protocols are periodic — they re-send everything on a timer, and silence means death. Everything designed after 1990 is event-driven — send once, acknowledge, then say nothing until something actually changes, and detect failure with a separate, much faster hello. That shift is most of why convergence went from minutes to milliseconds.
09 — Inside one router

How the winner is actually chosen

Four protocols offer the same prefix. Which one wins?

Protocols only propose. A single router may hear about the same destination from four of them at once.

01

Longest prefix match

Decided before any protocol matters. 10.1.2.0/24 beats 10.0.0.0/8 for one packet, even if the /8 came from a “better” protocol. More specific always wins.

02

Administrative distance

Same prefix from two protocols? The one you trust more wins. Cisco’s defaults: connected 0, static 1, eBGP 20, EIGRP 90, OSPF 110, IS-IS 115, RIP 120, iBGP 200. It is a local preference, not part of any protocol.

03

Metric

Same prefix, same protocol, two paths? Now the protocol’s own arithmetic decides — OSPF cost, IS-IS metric, EIGRP’s composite, RIP hops. Metrics are never comparable across protocols, which is exactly why step 02 exists.

04

RIB, then FIB

The winner is installed in the routing table (RIB) — the control plane’s conclusion. It is then compiled into the forwarding table (FIB) in hardware, which is what actually moves packets, millions per second, without consulting anything.

10 — Quick reference

The protocols, with their real numbers

Which protocol should I expect to find where?

ProtocolFamilyMetricRuns onConvergesWhere you meet it
RIPv2 IGP · distance vector hop count, max 15 UDP 520 · 224.0.0.9 ~30 s per hop small flat networks, textbooks
EIGRP IGP · distance vector composite: bandwidth + delay IP proto 88 sub-second Cisco enterprise estates
OSPFv2/v3 IGP · link state cost = ref-bandwidth ÷ link IP proto 89 tens of ms enterprise, data centres
IS-IS IGP · link state wide metric, 24-bit directly on layer 2 tens of ms carrier cores, large fabrics
BGP-4 EGP · path vector policy first, AS_PATH second TCP 179 seconds to minutes every network boundary on earth

Two of these have outlived their category. IS-IS won the carrier core because it doesn’t ride on IP and can carry new address families without a new protocol version. BGP escaped the border entirely — it now distributes VPN routes, EVPN MAC addresses and data-centre fabrics inside networks, doing a job its name says it shouldn’t.

Part III
Inside one subnet

One broadcast domain — the set of machines that reach each other with no router in between. Everything above this was about getting between such places; this is what happens inside one.

11 — Inside one subnet

The last place you can shout

How does a machine find the MAC address for an IP it has never talked to?

A subnet is not an addressing convention, it is a scope — and inside it you can ask a question of everyone at once. That single ability is what makes address resolution work, what lets a switch learn without ever being configured, and what eventually stops the whole thing scaling.

host A 10.0.1.9 · aa:…:01 switch forwarding table host B 10.0.1.5 · bb:…:05 hosts C · D “who has 10.0.1.5?” · dst ff:ff:ff:ff:ff:ff learns aa:…:01 is on port 1 — from the source field flooded to every other port — nobody said where .5 is “I do” · dst aa:…:01 · unicast, not broadcast learns bb:…:05 is on port 3 forwarded to one port only 1 2 3 4 5 two addresses learned, nothing configured — the table is a side effect of traffic
1A shouts. It has an IP and needs a MAC, so it sends an ARP request to the broadcast address — every machine in the subnet must look at it.
2The switch learns A for free. Not from the question, from the envelope: the frame’s source address plus the port it arrived on.
3It floods the question. Nobody has ever told it where 10.0.1.5 lives, so it sends the frame everywhere except back the way it came.
4B answers, quietly. The reply is unicast — B knows exactly who asked. The switch learns B from that frame’s source.
5And now it forwards. One port, not four. The next conversation between A and B never troubles anybody else.
Two machines learned each other’s addresses and a switch built a forwarding table, and nothing in that exchange was configured by anyone. That is the bargain of a broadcast domain: no configuration, paid for by everyone having to listen.
ARP, concretely
  • Request is broadcast, reply is unicast. Only the question costs everybody something.
  • The answer is cached in the neighbour table — ip neigh — with states you can watch: REACHABLE, STALE, DELAY, PROBE, FAILED.
  • IPv6 has no ARP. NDP does the same job over ICMPv6 multicast, so only the machines that might care are interrupted.
  • Gratuitous ARP is an unsolicited announcement — “this address is mine now”. It is how a floating IP tells a subnet it has moved.
How a switch learns
  • It reads the source address of every frame and notes which port it came from. Nothing else.
  • Unknown destination? Flood it. Not a failure — the only correct thing to do, and how the table fills.
  • Entries age out, typically after five minutes. The table is a cache, not a record.
  • Which is why a switch needs no configuration to work, and why a misconfigured one still forwards.
BUM, and the ceiling
  • Broadcast, Unknown unicast, Multicast — the three kinds of frame that go everywhere.
  • Every host’s NIC and CPU pays for every one of them, whether or not it cares.
  • The MAC table is finite. Overflow it and the switch floods permanently, because it can no longer remember where anything is.
  • This, not address space, is what limits how big a subnet can usefully be.
And loops here are fatal, not merely bad. An Ethernet header has no TTL. A broadcast frame that finds a loop circulates until the link saturates — and since it is being flooded, it multiplies at every switch on the way round. That is why spanning tree exists: it finds the loops and blocks the redundant links, which means paying for bandwidth you are then forbidden to use. Radia Perlman, 1985, and it is the grievance every design in the next part is trying to settle.
So where does a subnet end? At a router — that is the whole definition, and the netmask has almost nothing to do with it. /24 is not a property of the wire; it is the number a host consults to answer exactly one question: is this destination somewhere I can shout at, or do I hand the frame to my gateway? Get the mask wrong on one host and it ARPs for addresses nobody will answer, or routes to things that were sitting beside it all along.
Part IV
One wire, many networks

Section 05 solved this for routes: give every tenant its own table and carry an identifier with the packet. Two layers down, the same answer gets rebuilt twice — once in twelve bits, once in twenty-four.

12 — VLAN

Twelve bits that split a switch

How does one cable carry several separate networks?

One physical Ethernet, many broadcast domains. A four-byte tag, inserted on the way in and removed on the way out, so the hosts on either end never learn they were sharing.

switch A access ports switch B access ports trunk — tagged host · VLAN 10 10.1.1.5 host · VLAN 20 10.1.1.5 host · VLAN 10 10.1.1.6 host · VLAN 20 10.1.1.6 untagged untagged 0x8100 · VID 20 someone else’s Ethernet frame 0x8100 · VID 10 Ethernet frame both VLANs use 10.1.1.0/24 · neither can see the other · one cable carries both the tag exists only between the switches
1Untagged out of the host. It has no idea a VLAN exists — this is an ordinary Ethernet frame.
2The access port pushes the tag. That port is configured for VLAN 10, so the switch inserts four bytes the host never asked for.
3Across the trunk, tagged. VLAN 20’s traffic is on the same wire at the same time. The tag is the only thing keeping them apart.
4The egress access port pops it. Switch B strips the tag, because the port it is leaving by belongs to VLAN 10.
5Untagged into the host. It never learns it was tagged, which is why a host needs no VLAN configuration to sit in one.
The tag is pushed at the ingress access port and popped at the egress one, so it exists only between switches. An access port belongs to exactly one VLAN; a trunk carries many and must be told which. Get that mismatched at one end and you have built a black hole that pings fine.
complete
TPID 0x810016 bits
PCP3 bits · priority
DEI1 bit
VID12 bits · 4094 usable

Four bytes, wedged in after the source MAC — so a tagged frame is 1522 bytes on a 1500-byte network, which is why switches quietly grew a “baby giant” MTU. PCP is the class-of-service field every QoS design starts from. VID is the whole point, and its twelve bits are the reason the rest of this part exists: 0 and 4095 are reserved, leaving 4094 segments per L2 domain.

On Linux
  • ip link add link eth0 name eth0.100 type vlan id 100 makes a subinterface — its own netdev, MAC, MTU and sysctls, tagging everything it sends.
  • A bridge can do it the switch way instead: bridge vlan add dev eth0 vid 100 pvid untagged gives you real access and trunk ports.
  • NICs insert and strip tags in hardware, so tcpdump on the host often shows no tag at all on a frame that definitely had one.
Where it bites
  • The native VLAN — the one VLAN that travels untagged on a trunk — is a mismatch waiting to happen, and the basis of the classic double-tagging attack.
  • QinQ (802.1ad, TPID 0x88a8) stacks a provider tag over the customer’s. Same trick as an MPLS label stack, four bytes at a time.
  • Every switch along the path must carry the VLAN. The domain has to be physically contiguous, which is how a VLAN quietly becomes a cabling constraint.
Why it ran out
  • 4094 is small. A cloud region has more tenants than that before lunch.
  • Spanning tree blocks links to keep the L2 domain loop-free, so a fabric buys bandwidth it is not allowed to use.
  • Every switch in the domain learns every MAC in it. The table is finite and the failure mode is flooding.
  • A VLAN cannot cross a router, so an L2 segment cannot span two datacentres — which is exactly what people wanted next.
13 — VXLAN

An Ethernet segment inside a UDP packet

What do you do when 4,094 VLANs isn’t enough?

Wrap the whole frame in an ordinary IP packet and the L2 domain stops being a cabling problem. Twenty-four bits of tenant id instead of twelve, and a routed fabric underneath that can use every link it has.

what the guests see — one flat L2 segment · VNI 5000 10.1.1.5 10.1.1.6 10.1.1.7 10.1.1.8 what the wire actually carries — 50 bytes of it is new outer eth outer IP UDP 4789 VNI 5000 the original frame, untouched host A bridge + vxlan0 · VTEP eth0 · 10.0.0.11 host B bridge + vxlan0 · VTEP eth0 · 10.0.0.12 routed fabric ECMP, no STP the underlay sees one thing: a UDP packet from 10.0.0.11 to 10.0.0.12
A VTEP — the tunnel endpoint, in the host kernel or in a switch ASIC — wraps each frame and picks a remote VTEP to send it to. The fabric between them is plain routed IP, so every link is usable and spanning tree is gone. The 24-bit VNI is the tenant id: 16.7 million segments instead of 4094.

The encapsulation costs 50 bytes — 14 outer Ethernet, 20 outer IP, 8 UDP, 8 VXLAN — so a guest on a 1500-byte underlay must be told to use 1450, or the fabric must run jumbo frames. Nothing announces this. It presents as small packets working and large ones vanishing, which is the single most common VXLAN outage there is.

# a VTEP, with no flooding and no multicast
$ ip link add vxlan0 type vxlan id 5000 dstport 4789 local 10.0.0.11 nolearning
$ bridge fdb show dev vxlan0
00:1a:4b:7c:2e:91 dst 10.0.0.12 self permanent   # inner MAC → remote VTEP
00:00:00:00:00:00 dst 10.0.0.13 self permanent   # all-zero = flood to this VTEP too

Note dstport 4789. Linux’s VXLAN driver predates the IANA assignment and still defaults to 8472 — two vendors, two ports, no packets, and nothing in the logs to say so.

Who knows where a MAC lives
  • Flood and learn. Multicast in the underlay, classic Ethernet behaviour. Requires multicast, which most fabrics do not offer.
  • Head-end replication. A static list of remote VTEPs; unknown frames are unicast to each in turn. Simple, and it scales like n².
  • EVPN. A control plane instead of flooding — and it is BGP again, advertising MACs the way it advertises prefixes.
EVPN, concretely
  • Type 2 — a MAC, optionally with its IP. “This address is behind me.” Replaces learning entirely.
  • Type 3 — inclusive multicast: “I have VNI 5000, send me its broadcast.” Builds the flood list automatically.
  • Type 5 — an IP prefix, for routing between VNIs rather than bridging within one.
  • RD and RT do the same jobs they do in L3VPN — uniqueness and membership — because it is the same machinery.
What it costs
  • Encapsulation in software is real CPU. Offload (tx-udp_tnl-segmentation) moves it into the NIC; without it, throughput falls off a cliff.
  • The outer UDP source port is a hash of the inner flow — that is the only entropy the underlay has for ECMP. A NIC that gets it wrong pins every tunnel to one path.
  • Your troubleshooting tools now lie: a tcpdump on the underlay shows UDP, not the traffic you care about.
  • Broadcast and unknown-unicast still happen, and now they cost a replication to every VTEP that holds the VNI.
Read this next to section 05 and it is the same architecture twice. The VNI is the VPN label; the VTEP is the PE; the routed fabric is the P core that knows nothing about tenants; MP-BGP distributes membership in both. MPLS built it for carriers with label switching in the middle; VXLAN rebuilt it for datacentres with plain IP in the middle, because every device already knew how to forward that. Give each tenant its own table, and make the identifier travel with the packet — that is the whole idea, at every layer this guide visits.
14 — EVPN

The underlay has to stay boring

What tells every switch where a tenant’s addresses are?

VXLAN moves the packet. Something still has to decide where to send it — and that job is split across two routing protocols that must never be handed each other’s work.

Underlay IGP, or eBGP loopbacks and links only Overlay MP-BGP EVPN every tenant route EVPN sessions bind to a loopback from that table the overlay rides the underlay, never the reverse one leaf switch global routing table ~36 routes — and that number does not grow with tenants VRF blue 10.0.0.0/8 and its endpoints · imported by RT blue VRF red 10.0.0.0/8 and its endpoints · imported by RT red tenant churn never touches the global table — which is the entire point
Three tables in one box, fed by two protocols that never mix. Two of them hold the same prefix. The dashed arc is the dependency that decides everything else: the sessions carrying tenant routes are built on a loopback the underlay made reachable — so if tenant churn could shake the underlay, it would shake the sessions carrying the tenants.
Route typeCarriesWhat it replaces
Type 2a MAC, optionally with its IP and its VNIflood-and-learn, and ARP flooding with it
Type 3inclusive multicast — “I hold VNI 5000, send me its broadcast”hand-built flood lists and underlay multicast groups
Type 5an IP prefix, in a VRFrouting between subnets rather than bridging within one
Type 1 · 4Ethernet auto-discovery and Ethernet segment routesthe guesswork in dual-homing a server to two leaves
Two kinds of VNI
  • Each subnet gets an L2VNI — its bridge domain. Traffic within a subnet is encapsulated in it.
  • Each VRF gets one L3VNI — its routing domain on the wire. Traffic between subnets is encapsulated in that instead, and the egress leaf does its own lookup on arrival. This is symmetric IRB.
  • The alternative, asymmetric IRB, encapsulates into the destination subnet’s L2VNI — which forces the ingress leaf to hold ARP state for every subnet it might ever send to. It works, and it stops scaling early.
  • So a tenant with four subnets has four L2VNIs and exactly one L3VNI.
The anycast gateway
  • The same gateway address and the same MAC are configured on every leaf holding that subnet.
  • So a VM’s default gateway is always on its own leaf, one hop away. Inter-subnet traffic is routed at the source rack and encapsulated straight to the destination — it never hairpins to a central router.
  • Move the VM to another rack and nothing in it changes: same gateway IP, same gateway MAC, no ARP re-resolution, no reconfiguration.
  • Combined with Type 2 routes, leaves also answer ARP locally instead of flooding it — the broadcast domain stops behaving like one.
Why not just use the IGP
  • Overlapping addresses are incoherent in an IGP. One database, one metric space, no VRF concept in flooding. BGP copes because the RD makes the NLRIs distinct and the RT files them.
  • Blast radius. An IGP floods to everyone and every router runs SPF on every change. Tenant endpoints in the IGP means every leaf recomputes its tree whenever a VM moves.
  • And the dependency. The BGP sessions carrying tenant routes are built on loopbacks the IGP advertises. Tenant churn reaching the IGP would shake the sessions carrying the tenants — a loop you cannot have.
Three things that bite, in rough order of how long they take to find. A route reflector holds no VRF for your tenants, so RT import finds no match and the default behaviour on many platforms is to discard the route — silently breaking the overlay until route-target filtering is disabled on the RRs. Route distinguishers should stay auto-derived per switch: that is what lets a reflector keep several paths to one prefix from different leaves, and pinning a single RD across leaves collapses your ECMP at the reflector. And the underlay MTU must carry the tenant MTU plus the encapsulation, or you get the familiar shape — pings fine, large transfers vanish.
Part V
When the network becomes software

The last three metres, where a cable is a pointer, a stack is a struct, a routing table is a device and a tenant is a number in a map. Not all of it is host-scoped — one of these is a router concept and one is a cluster concept — but this is where you meet them as code.

15 — Virtual Ethernet

A cable that is really a pointer

How does a container’s network reach the outside world?

Below the NIC, the same problem repeats at a thousandth of the scale: two network stacks on one machine that need a link between them. The kernel’s answer is a pair of devices that are each other’s transmit path.

netns · pod netns · host socket · application TCP / UDP IP output · route lookup qdisc · noqueue eth0 veth_xmit() veth9a3f21 napi_gro_receive() tc ingress · eBPF netfilter conntrack · nat · rules bridge cni0 — or IP route eth0 → the wire veth_xmit() → rcu_dereference(peer) then straight into the peer’s receive path no wire · no queue · no copy · same CPU, same softirq tc-BPF · bpf_redirect_neigh()
A veth pair is not a cable. veth_xmit() follows an RCU pointer to its peer and hands the skb to that device’s receive path — napi_gro_receive() where GRO or XDP is enabled, otherwise the per-CPU backlog. The crossing itself is nearly free; the cost is that the packet traverses a full network stack twice, once per namespace. A tc-BPF program at the host-side ingress hook is how that second traversal gets skipped.

Create one and you get two devices at once — ip link add v0 type veth peer name v1 — because they are one kernel object with two faces. Put one end in a container’s namespace, leave the other in the host’s, and you have the standard pod interface. Delete either end and both disappear.

# in the pod — which host device is the other end?
$ cat /sys/class/net/eth0/iflink
12

# on the host — that ifindex, and the namespace it reaches back into
$ ip -d link show | grep -A1 '^12:'
12: veth9a3f21@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master cni0
    link/ether 5a:c2:1f:0b:77:e4 link-netnsid 0

@if2 is the peer’s ifindex in the other namespace — the pod’s eth0. That cross-reference is the only reliable way to map a container interface to its host-side device when you are staring at tcpdump output on a busy node.

What it costs
  • Two stack traversals per packet — routing, netfilter and conntrack run again on the host side. This, not the handoff, is the overhead.
  • GSO passthrough: 64 KB super-packets cross unsegmented, so bulk throughput looks excellent while packets-per-second workloads do not improve at all.
  • Checksums are never computed. The link cannot corrupt anything, so the stack skips it — which is why tcpdump reports “incorrect” checksums on a perfectly healthy pod.
  • veth is set IFF_NO_QUEUE, so the default qdisc is noqueue. Shaping has to be added deliberately, on the host-side device, usually tbf plus an ifb for the other direction.
What surprises people
  • Moving a device into a namespace brings it down and flushes its addresses and routes. Configure after the move, never before.
  • Per-namespace sysctls reset too — rp_filter, accept_ra, forwarding are all properties of the new netns, not the device.
  • Counters read backwards: the host-side veth’s RX is the pod’s egress. Half of all pod bandwidth dashboards have this inverted.
  • MTU is per device and both ends must agree, or you get a black hole that only shows up on large frames.
  • With br_netfilter loaded, bridged frames traverse iptables as well — surprising, required by kube-proxy, and a real cost per packet.
When you reach past it
  • tc-BPF redirect. Cilium’s bpf_redirect_peer() drops a pod-to-pod packet straight into the target namespace, and bpf_redirect_neigh() sends it to the wire — skipping the host’s routing and netfilter entirely. Same veth, half the work.
  • macvlan / ipvlan. One device, no bridge, no second traversal — at the price of the host being unable to filter or even talk to the pod.
  • SR-IOV VF. A hardware slice of the NIC handed to the pod. The kernel path disappears, and so does everything built on it.
  • tap is the sibling that delivers to userspace instead of another namespace — the same idea, terminating in a VM’s virtio-net rather than a second stack.
On a GPU node this path is the slow lane on purpose. RoCE traffic never enters it: the verbs datapath bypasses the kernel stack completely, which is why an RDMA pod gets an SR-IOV VF alongside its veth rather than instead of it. The veth carries control traffic, health checks and anything that needs a Service IP; the fabric carries the tensors, and the two never meet in the stack.
16 — Network namespaces

Not a filter over the stack — another copy of it

What exactly does a container get its own copy of?

The kernel’s answer to tenancy is the most thorough one in this guide and the only one that puts nothing on the wire at all.

netns · pod netns · host devices — lo, eth0 routing tables — local, main, default neighbour / ARP table netfilter rules + conntrack sysctls — rp_filter, forwarding, … TCP / UDP port space sockets devices — lo, eth0 (the real NIC), vethXYZ routing tables — local, main, default neighbour / ARP table netfilter rules + conntrack sysctls — rp_filter, forwarding, … TCP / UDP port space sockets veth pair the only way across every row is a separate object · nothing in any packet says which column it came from
Both columns can hold a device called eth0 carrying 10.0.0.1, both can bind :80, and a conntrack entry in one is invisible in the other. The kernel object is struct net; every device, socket, route and netfilter hook holds a pointer to exactly one of them, and that pointer is the isolation.
# a namespace is a reference, not a file — `ip netns add` bind-mounts one so it survives
$ ls -l /proc/4821/ns/net
lrwxrwxrwx 1 root root 0 → 'net:[4026533871]'

# enter a running pod's namespace without its filesystem, its cgroups or its binaries
$ nsenter -t 4821 -n ip route
default via 10.244.3.1 dev eth0
10.244.3.0/24 dev eth0 proto kernel scope link src 10.244.3.47

That inode number is the namespace’s real identity. A namespace lives while something still references it — a running process, or a bind mount under /var/run/netns — and is destroyed when the last reference goes. On destruction its virtual devices are deleted and any physical device it held is returned to the initial namespace rather than disappearing with it.

What it buys
  • Total isolation, not just routing. Firewall rules, conntrack, sysctls, port space and sockets are all separate — the strongest boundary in this guide.
  • Overlapping addresses are free and need no encapsulation, because nothing is shared to collide in.
  • It is the unit Kubernetes builds on: a pod is a namespace. The pause container exists to hold it open, which is why containers in one pod share localhost and a single port space.
What it costs
  • Crossing a boundary needs a device, so every packet pays the two full stack traversals from section 15.
  • Each namespace carries its own conntrack accounting and its own copy of every sysctl you thought you had set. A node-wide tuning change reaches none of them.
  • Debugging tools are namespace-blind by default: ss, tcpdump and conntrack -L all report the namespace you happen to be in, which is usually the wrong one.
The thing to notice
  • VLAN puts twelve bits in the frame. MPLS puts a label in the packet. VXLAN puts a VNI in a UDP header.
  • A namespace puts nothing anywhere. There is no tenant identifier on the wire, because there is no shared wire — the packet never touches a structure belonging to anyone else.
  • That is why it is both the cleanest isolation available and completely unable to cross a machine. The moment you need two hosts, you are back to VXLAN.
17 — VRF on Linux

The same idea, one layer down

How does one Linux box hold several routing tables?

You have already met VRF, in section 05, as what a provider edge router keeps per tenant. It is not a host concept and has no scale of its own — a PE, a switch and a laptop all mean the same thing by it. What is specific here is the implementation, and what it buys: separate routing tables, but one firewall, one conntrack and one agent that can see all of them.

one network namespace — one stack shared by every VRF ↓ netfilter + conntrack sockets + port space sysctls ip rule 1000 · from all lookup [l3mdev-table] — one rule, selecting by the device’s L3 master table 10 vrf-blue table 20 vrf-red table 254 main eth1 — enslaved eth2 — enslaved eth0 — unenslaved both tenants may carry 10.0.0.0/8 · the same firewall sees both
Compare this with the previous figure. There, everything was duplicated. Here only the routing table is, and the top row — netfilter, sockets, sysctls — is shared by all of them. That is a feature when you want one firewall policy across tenants, and a hazard when a conntrack entry created in one VRF is matched in another.
$ ip link add vrf-blue type vrf table 10
$ ip link set vrf-blue up
$ ip link set eth1 master vrf-blue      # eth1's traffic now resolves in table 10

$ ip rule show
0:      from all lookup local
1000:   from all lookup [l3mdev-table]     # one rule covers every VRF
32766:  from all lookup main
32767:  from all lookup default
How selection works
  • A VRF is an l3mdev device. Enslaving an interface to it makes the kernel resolve that interface’s traffic in the master’s table.
  • Priority 1000’s [l3mdev-table] is a single rule standing in for all of them — add a hundred VRFs and it stays one rule.
  • The routing policy database underneath predates VRF and is more general: ip rule can select a table by source, input interface, fwmark or uid. VRF is the tidy special case that won.
The gotcha that costs a day
  • A socket is not in a VRF unless you put it there. An unbound listener uses the default table and silently ignores your VRFs.
  • Fixes: SO_BINDTODEVICE to the VRF device, or ip vrf exec vrf-blue <cmd>, which attaches a small BPF program to bind the process’s sockets for you.
  • To let one listener serve every VRF, set net.ipv4.tcp_l3mdev_accept=1 and the UDP equivalent — after which you must read the incoming device to know which tenant you are talking to.
Where you meet it
  • Linux as a router — FRR with VRFs is the open-source PE from section 05, with the same RD and RT configuration.
  • Splitting a management network from a data network on a host that must never route between them.
  • Keeping a storage or RoCE network off the default table so a mis-set default route cannot pull GPU fabric traffic onto the pod network.
  • Anywhere a namespace would be too much — because you still want one firewall, one metrics exporter, one agent that can see it all.
18 — Identity, not address

When the address stops meaning anything

How do you write a firewall rule when addresses are recycled every minute?

Every mechanism so far assumes an address identifies a tenant. In a cluster it identifies nothing: pod addresses are handed out from a node’s range and reused within seconds.

pod · labels app=api env=prod team=blue identity 51234 one per unique label set allow 51234 → 51987 : 8080 the rule names identities, never addresses overlay — VXLAN or Geneve the identity rides in the encapsulation header, so it arrives with the packet [ outer IP | UDP | VNI + identity | inner ] direct routing — no encapsulation nothing extra on the wire; the destination node looks the source address up in its own map ipcache: 10.244.3.47 → 51234 either way the decision is made on identity — the address is only ever a lookup key
A workload’s labels are collapsed into a numeric security identity, distributed cluster-wide, and policy is compiled to identity pairs. Two pods with identical labels on twelve different nodes share one identity and therefore one rule — which is why policy size tracks the number of kinds of workload, not the number of pods.
Why addresses failed here
  • A pod address is allocated from its node’s CIDR and returned to the pool on termination. It is a location, not a name. A rule written against 10.244.3.47 protects whatever lands there in ninety seconds.
  • Rules would also have to be rewritten on every scale event. Identity moves the churn into a map that changes without touching policy.
  • Reserved identities cover what has no labels: host, remote-node, world for everything outside the cluster, and unmanaged.
The trust model inverts
  • In L3VPN the core physically cannot mix two tenants — their routes live in different tables, so isolation survives a misconfiguration.
  • A Kubernetes pod network is flat by default: every pod can reach every pod, and NetworkPolicy is opt-in. Isolation exists only while the policy is right.
  • So this is the one mechanism in the guide where tenancy is a decision evaluated per packet rather than a structure that makes leaking impossible. Enormous flexibility, and nothing underneath it if the policy engine is wrong or absent.
Part VI
A datacentre, end to end

Everything above, applied once to a real building: 64 GPU nodes, 8 storage nodes, twelve racks, and tenants who must not be able to reach each other.

19 — A worked design

A neocloud, built

What does all of this look like in one actual building?

A concrete build, with the arithmetic shown. Numbers are worked from stated assumptions rather than quoted — change an assumption and the shape changes with it.

The build. 64 compute nodes, 8 GPUs apiece — 512 GPUs — plus 8 storage nodes, in 12 racks. Nodes are treated as one class at HGX density, roughly 15 kW each. Mix in lower-power inference nodes and they pack denser, which shrinks the rack count but leaves every fabric below unchanged.
transit · peering edge — the only way out 2 × border eBGP upstream 2 × firewall stateful edge 4 × SoftGate NAT · EIP · L4LB Backend rail-optimised RoCEv2 8 rail leaves + 8 spines 1:1 — non-blocking Frontend EVPN / VXLAN 18 leaves + 2 spines one VPC = one VRF Storage NVMe-oF 2 leaves · jumbo frames its own VRF Out-of-band BMC / Redfish 9 ToR + 2 agg never routed to a tenant 8 × 400G 2 × 200G 2 × 100G 1 × 1G compute · 8 racks · 64 nodes, 512 GPUs 8 nodes a rack · ~120 kW · liquid cooled storage · 1 8 nodes network · 3 ≈ 89U of gear frontend ToR OOB rail + spine routers · FW · SoftGate
Every compute rack feeds four separate fabrics, and only one of them — the frontend — reaches the edge. That is most of the security posture, expressed as cabling rather than rules. The backend carries no tenant identifier at all, and the out-of-band network exists precisely so that a node whose data plane is broken is still reachable.

Twelve racks: 8 compute, 1 storage, 3 network and edge. Power sets that count — an 8-GPU node draws roughly 15 kW, so eight of them is a 120 kW liquid-cooled rack, near the practical ceiling today. Everything downstream, including how many rails the backend needs, follows from that kilowatt figure.

BACKEND — RAIL-OPTIMISED every node puts GPU 0 on rail 0, GPU 1 on rail 1, and so on to 7 8 spines — every rail leaf connects to all of them, 1:1 rail 0 rail 1 rail 2 rail 3 rail 4 rail 5 rail 6 rail 7 node 1 · 8 NICs node 2 · 8 NICs node 3 · 8 NICs node 4 · 8 NICs … × 64 nodes 512 fibres in total, 64 landing on each rail FRONTEND — LEAF AND SPINE every leaf to every spine · every node to both leaves in its own rack spine 1 spine 2 leaf 1a leaf 1b leaf 2a leaf 2b rack 1 · node rack 2 · node 2 × 200G per node, split across the pair
The backend drawn as what it is — a matrix. Eight vertical rails, one horizontal line per node, and a dot wherever a NIC meets a rail. Every node touches every rail exactly once, so an all-reduce across GPU 3 stays inside rail 3 instead of crossing the fabric. The frontend needs no such discipline: any leaf, any spine, and a node dual-homed to its own rack’s pair for survivability rather than locality.

Half the switching never reaches those network racks. Thirty-one switches live in the racks they serve — two frontend leaves and one out-of-band switch per rack, plus the storage pair. What has to be central is everything that spans racks, and that is what fills the other three.

In the network racksCountU eachU
Backend rail leaves8216
Backend spines8216
Frontend spines224
OOB aggregation212
Border routers224
Firewalls224
SoftGates428
Patch panels~151–2~25
Cable managers, PDUs, blanking——~10
Total28 devices—≈ 89U

Two 42U racks give 84U, so this needs three — and the binding constraint is not the switches but the patching. 512 backend fibres converging on eight rail switches is the hard part of rail optimisation, and it is why those switches must be central: each one takes GPU i from every node in the cluster, so none of them can live in a compute rack. Power is comfortable either way — roughly 43 kW total, about 15 kW a rack across three.

FabricPer nodeSwitchingOversubscriptionTenant isolation
Backend8 × 400G8 rail leaves + 8 spines1:1whole nodes per tenant — or PKeys on InfiniBand
Frontend2 × 200G18 leaves + 2 spines3:1VPC → VRF, V-Net → L2VNI
Storage2 × 100G2 leaves2:1its own VRF, exported per tenant
Out-of-band1 × 1G9 ToR + 2 aggn/aoperator only — never reachable by a tenant
The backend arithmetic
  • 64 HGX nodes × 8 NICs = 512 endpoints at 400G.
  • Rail-optimised: eight rails, and GPU i on every node lands on rail i. A collective across GPU 0s stays inside one rail instead of crossing the fabric.
  • Each rail leaf takes 64 downlinks and 64 uplinks — non-blocking by construction. Eight spines, each with 8 ports to every rail leaf, fills a 64-port spine exactly.
  • 1:1, not 3:1. All-reduce is all-to-all, so oversubscription lands directly in job time. This is the one fabric where the enterprise default is simply wrong.
Where tenancy actually lives
  • Frontend only. One tenant = one VPC = one VRF, present on the leaves that hold their nodes and nowhere else.
  • Their jump host sits in their own VPC, reachable through a DNAT on a SoftGate — the per-tenant entry point, with the public side in the System VPC.
  • The backend has no VRFs. Isolation there is whole nodes allocated to one tenant, or PKeys if it is InfiniBand. Do not promise tenants a shared, sliced RoCE fabric.
  • Overlapping 10.0.0.0/8 between tenants is free on the frontend and meaningless on the backend.
What decides the numbers
  • Power per rack sets node density, which sets rack count, which sets leaf count. Everything else is downstream of a kilowatt figure.
  • GPU:NIC affinity. A NIC must sit on the same PCIe root complex as the GPU it serves, or you pay for every transfer twice. This constrains server SKU before it constrains the network.
  • MTU end to end. 9000 on the backend and storage, and an underlay that carries tenant MTU plus encapsulation on the frontend.
  • Cable count. 64 nodes × 8 backend NICs is 512 optics before a single tenant port. Optics and their failure rate are a real line item, not an afterthought.
Three things that will bite, in order. The out-of-band network is the one that must never fail — it is how you reach a node whose data plane is broken, so it gets its own switches, its own power feed and no route to any tenant VRF. Storage is east-west traffic wearing a north-south costume: eight nodes serving 64 clients will saturate a 2:1 storage fabric during checkpointing long before anyone complains about the tenant network. And the backend fabric cannot be resized later — rail-optimised cabling is decided at build time, so the number of rails you can grow to is a decision you are making now, whether or not you know it.
Coda
One idea, five times

The guide has now asked the same question at five scales and been given five different answers. Read together, they disagree about something fundamental.

Coda

Where the tenant identifier lives

Is there a single idea underneath all of it?

 VLANMPLS L3VPNVXLANnetns · VRFidentity
identifierVIDVPN labelVNIstruct net · table idsecurity identity
width12 bits20 bits24 bits32 bitsunbounded
assigned bythe operatorthe egress PEthe orchestratorthe kernelthe control plane
on the wire?a tag in the framea shim in the packeta UDP headernothingsometimes
shared core seesa tagged framea labela UDP flowthe same kernelan address
boundary isa broadcast domaina VRF tablea bridge domaina namespacea policy engine
crosses a machine?yes, one L2 domainyes, globallyyes, over any IPnoyes, with the map
fails as4094, and spanning treea wrong route targetan MTU black holethe wrong table bounda stale identity
where the tenant identifier lives VLAN MPLS L3VPN VXLAN netns · VRF identity in the packet nowhere on the wire the core understands the identifier, and nothing else separate tables make leaking between tenants structurally impossible the core understands everything, and carries no identifier at all isolation exists only for as long as the policy is correct
The trade is not about scale — 4094 versus 16 million is the least interesting row in the table. It is about what you are asking the shared infrastructure to do: understand a tenant identifier and nothing else, or understand everything and be trusted not to mix it up.
So the guide’s own refrain is wrong, and usefully so. “Make the identifier travel with the packet” holds for the first three columns and breaks twice at the bottom: a namespace puts nothing on the wire, and identity-based policy resolves the tenant after the packet has already arrived. The honest version is one level up — every scheme picks somewhere to keep the tenant identifier, and that choice decides whether isolation is a property of the structure or a property of the configuration. MPLS chose the structure and pays for it in protocols. Kubernetes chose the configuration and pays for it in blast radius. Neither is the mistake; picking one without knowing which you picked is.
Reference

Glossary

Every term the guide leans on more than once. Each also appears as a dotted underline at its first use in any section — hover it there instead of coming here.

Adj-RIB-In
What a BGP peer actually sent, stored untouched before any import policy runs, so policy can be re-applied without asking again.
AS_PATH
The list of autonomous systems a BGP route has crossed. Both the loop check — see your own number, discard — and the distance metric.
BFD
Bidirectional Forwarding Detection. A bare sub-second heartbeat that other protocols subscribe to instead of running their own slow timers.
DUAL
EIGRP's Diffusing Update Algorithm. Keeps a provably loop-free backup so failover needs no conversation with anyone.
ECMP
Equal Cost Multi-Path — spreading traffic over several equally good paths.
FEC
Forwarding Equivalence Class: a set of packets a router will treat identically. An MPLS label is the name of one.
FIB
The forwarding table, compiled into hardware from the RIB. What actually moves packets, consulting no protocol.
IRB
Integrated Routing and Bridging — doing both in one device. Symmetric IRB routes into an L3VNI; asymmetric uses the destination subnet's L2VNI.
L2VNI
One per subnet — the bridge domain. Traffic within a subnet is encapsulated in it.
l3mdev
The Linux VRF device. Enslave an interface to it and that interface's traffic resolves in the master's routing table.
L3VNI
One per VRF. Traffic routed between subnets is encapsulated in it, and the egress leaf does its own lookup on arrival.
LDP
Label Distribution Protocol. Hands out a label for every prefix already in the IGP, following its shortest path.
LER
Label Edge Router. Classifies a packet into a FEC once, at the edge, and pushes the first label.
Loc-RIB
What a BGP speaker believes after import policy and best-path selection: one winner per prefix.
LSA
Link State Advertisement. One router's description of its own links, flooded unchanged and acknowledged.
LSDB
The link-state database: every LSA in an area, byte-identical on every router in it.
LSR
Label Switch Router. Swaps labels without ever looking at the IP header.
netns
A network namespace: a complete, independent copy of the network stack — devices, tables, netfilter, sysctls and port space.
PHP
Penultimate Hop Popping. The second-to-last router removes the label so the egress does one lookup instead of two.
RIB
The routing table: the control plane's chosen route per prefix, one winner across all protocols.
SPF
Shortest Path First — Dijkstra's algorithm, run over the LSDB, rooted at the router running it.
veth
A pair of devices that are each other's transmit path. Not a cable — a pointer.
VNI
VXLAN Network Identifier: 24 bits naming a segment, so 16.7 million of them against a VLAN's 4094.
VTEP
VXLAN Tunnel Endpoint — where encapsulation starts and ends. A switch, a host, or a DPU.