Lesson 16 / 17
Redundancy and Failover
The failover window in gateway redundancy is measured: when the upstream link breaks and nobody has heard, all forty flows fall into a black hole one hop later; when the gateway fails over, the black hole clears but the peer link carries forty crossings; when two gateways each believe themselves active, all forty flows enter a loop and the peer link carries eighty crossings.
Contents
In a spine–leaf design, redundancy was structural: no device was the sole owner of a path, and the loss of a spine only changed how flows were distributed. On the host side, this is not the case. A host sends its outbound packet not to a path but to an address — the default gateway’s address. The address is singular, and at any moment it sits on exactly one device.
This singularity takes redundancy out of the structural category. When the device fails, a second device must take over the address, and everyone must hear about the takeover. This lesson’s question is what happens to the packet during that short interval while the hearing is still in progress.
The Virtual Address and Two Roles
Gateway redundancy puts two devices behind a single address. This address is called the virtual address; hosts know it as their default gateway and are unaware that two devices exist.
One of the devices is active, the other is standby. The active one answers for the virtual address and forwards packets upward; the standby one listens silently. When the active device’s periodic advertisements stop arriving, the standby waits for a period and then takes over the address.
# taught transcript, not run gateway pair virtual address single address, hosts' default gateway g1 priority 110 active -> answers the virtual address, forwards upward g2 priority 100 standby -> listens silently, waits for advertisements peer link g1 - g2, carries advertisements and state timing advertisement interval active side advertises once per interval dead timer standby takes over if no advertisement heard in this time failover instant standby begins answering for the virtual address leaf table (for remote networks) destination next hop u1 u2 u3 the gateway believed to currently hold the virtual address
Service-level failover patterns — health checks, retries, the circuit breaker — were established in the Resilience and Reliability course and are not repeated here. There, what failed over was a service instance, and what was measured was the request’s fate; here, what fails over is the gateway, and what is measured is the packet’s fate.
What the Window Is
Failover is not instantaneous. The time it takes for the dead timer to expire, for the standby to announce the address, and for switches along the path to relearn where the address is — the sum of these is called the failover window. During the window, devices disagree about where the virtual address is.
Inside the window there are two separate questions. First: who answers for the virtual address? Second: can the answering device forward the packet? The measurement counts exactly this distinction: a gateway whose upstream link has broken keeps collecting packets for as long as it holds the address.
The assumptions the measurement rests on:
- TD17 — Nine leaves (
y1-y9) connect to a gateway pair (g1,g2); the pair connects to a core (c), and the core connects to three remote networks (u1,u2,u3). The forty flows have a leaf as their source and a remote network as their destination; the traffic measured is entirely north-south. - TD18 — The failure measured is the breaking of the
g1-cupstream link.g1is healthy, remains connected to the leaves, and can continue to hold the virtual address. - TD19 — Every leaf holds a belief: whether the virtual address is on
g1org2. A leaf that has heard saysg2; one that has not saysg1. - TD20 — Every gateway also holds a belief. A gateway that believes itself active forwards the packet upward; one that believes its peer active hands the packet to its peer over the peer link.
- TD21 — The oracle is known because we built the setup ourselves: after
g1-cbreaks, the only path to the remote networks is throughg2. - TD22 — The regimes differ only in belief; the topology does not change except for the
presence of the
g1-clink. So the difference measured is a difference in information, not in structure. - TD23 — The “peer link” column counts how many times the forty flows cross the
g1-g2link in total. This link is sized for control traffic; it carrying user traffic is a symptom. - TD24 — A packet is counted as a loop when it crosses the same link a second time or burns through the hop limit (12); a packet whose next hop is no longer a neighbor is counted as a black hole.
- TD25 — The hops column counts only the hops spent; a packet that falls into a black hole has spent the hops up to where it died.
- TD26 — In a set of forty flows, the smallest measurable difference is 1/40 = 0.025.
The Measurement
"""Gateway redundancy: the packet's fate during the failover window. Part 1 - regimes: converged, window, upstream-link tracking, split-brain. Part 2 - the change in fate as the number of leaves that have heard grows. """ SEED = 20260810 HOP_LIMIT = 12 LEAF = [f"y{i}" for i in range(1, 10)] REMOTE = ["u1", "u2", "u3"] NODES = LEAF + ["g1", "g2", "c"] + REMOTE LINKS = ([(y, g) for y in LEAF for g in ("g1", "g2")] + [("g1", "g2"), ("g1", "c"), ("g2", "c")] + [("c", u) for u in REMOTE]) BROKEN = [b for b in LINKS if b != ("g1", "c")] def make_rng(seed): state = seed % 2147483646 + 1 def rand(n): nonlocal state state = (state * 48271) % 2147483647 return state % n return rand def neighbors(links): k = {u: set() for u in NODES} for x, y in links: k[x].add(y) k[y].add(x) return k def build_tables(leaf_belief, g1_belief, g2_belief): """The next hop each node holds for the remote networks.""" t = {} for y in LEAF: for u in REMOTE: t[(y, u)] = leaf_belief[y] for g, peer, belief in (("g1", "g2", g1_belief), ("g2", "g1", g2_belief)): for u in REMOTE: t[(g, u)] = "c" if belief == g else peer for u in REMOTE: t[("c", u)] = u return t def forward(source, dest, tab, links): k, u, crossed, hops = neighbors(links), source, [], 0 while u != dest: if hops >= HOP_LIMIT: return "loop", hops nxt = tab.get((u, dest)) if nxt is None or nxt not in k[u]: return "black hole", hops if (u, nxt) in crossed: return "loop", hops crossed.append((u, nxt)) u, hops = nxt, hops + 1 return "reached", hops def pairs(count=40, seed=SEED): rand, out = make_rng(seed), [] while len(out) < count: out.append((LEAF[rand(9)], REMOTE[rand(3)])) return out def peer_crossings(tab, links): """How many of the forty flows cross the g1-g2 peer link.""" k, count = neighbors(links), 0 for x, y in pairs(): u, crossed, hops = x, [], 0 while u != y and hops < HOP_LIMIT: v = tab.get((u, y)) if v is None or v not in k[u] or (u, v) in crossed: break if {u, v} == {"g1", "g2"}: count += 1 crossed.append((u, v)) u, hops = v, hops + 1 return count def measure(tab, links): counts, hops = {"reached": 0, "loop": 0, "black hole": 0}, 0 for x, y in pairs(): fate, a = forward(x, y, tab, links) counts[fate] += 1 hops += a return counts, hops def belief(heard): return {y: ("g2" if y in heard else "g1") for y in LEAF} ALL, NONE = set(LEAF), set() REGIME = [ ("converged, g1 active", LINKS, belief(NONE), "g1", "g1"), ("upstream link broke, nobody heard", BROKEN, belief(NONE), "g1", "g1"), ("g1 failed over, no leaves heard", BROKEN, belief(NONE), "g2", "g2"), ("g1 failed over, four leaves heard", BROKEN, belief(set(LEAF[:4])), "g2", "g2"), ("g1 failed over, all heard", BROKEN, belief(ALL), "g2", "g2"), ("both think themselves active", LINKS, belief(set(LEAF[:5])), "g1", "g2"), ("both think the peer active", LINKS, belief(set(LEAF[:5])), "g2", "g1"), ] print(f"{'regime':<35s} {'reached':>7s} {'loop':>6s} {'black hole':>10s} " f"{'hops':>5s} {'peer link':>9s}") for name, links, lb, a1, a2 in REGIME: tab = build_tables(lb, a1, a2) s, a = measure(tab, links) print(f"{name:<35s} {s['reached']:7d} {s['loop']:6d} {s['black hole']:10d} " f"{a:5d} {peer_crossings(tab, links):9d}") print() print(f"{'leaves heard':>12s} {'g1 did not fail over':>32s} {'g1 failed over':>32s}") print(f"{'':>12s} {'reached black hole hops peer':>32s}" f" {'reached black hole hops peer':>32s}") for n in range(0, 10): lb, columns = belief(set(LEAF[:n])), [] for g1b in ("g1", "g2"): tab = build_tables(lb, g1b, "g2") s, a = measure(tab, BROKEN) columns.append(f"{s['reached']:9d}{s['black hole']:11d}{a:5d}" f"{peer_crossings(tab, BROKEN):5d}") print(f"{n:12d} {columns[0]} {columns[1]}") print() print(f"nodes {len(NODES)}, links {len(LINKS)}, flows 40, " f"hop limit {HOP_LIMIT}")
regime reached loop black hole hops peer link
converged, g1 active 40 0 0 120 0
upstream link broke, nobody heard 0 0 40 40 0
g1 failed over, no leaves heard 40 0 0 160 40
g1 failed over, four leaves heard 40 0 0 146 26
g1 failed over, all heard 40 0 0 120 0
both think themselves active 40 0 0 120 0
both think the peer active 0 40 0 120 80
leaves heard g1 did not fail over g1 failed over
reached black hole hops peer reached black hole hops peer
0 0 40 40 0 40 0 160 40
1 4 36 48 0 40 0 156 36
2 7 33 54 0 40 0 153 33
3 9 31 58 0 40 0 151 31
4 14 26 68 0 40 0 146 26
5 18 22 76 0 40 0 142 22
6 24 16 88 0 40 0 136 16
7 29 11 98 0 40 0 131 11
8 34 6 108 0 40 0 126 6
9 40 0 120 0 40 0 120 0
nodes 15, links 24, flows 40, hop limit 12
Answering Is Not Forwarding
The second row is the lesson’s most important number. When g1-c breaks and nobody has heard,
all forty of the forty flows fall into a black hole. The hops spent are 40 — one hop per
flow. The packet leaves the leaf, arrives at the gateway holding the virtual address, and dies
there.
This is the shape of the failure: the device is up, its ports work, the virtual address answers, and the host finds its default gateway reachable. Only the packet never goes up. Who owns the virtual address and who can forward the packet are separate questions, and the host can only ask the first one.
Failing Over Turns the Black Hole into the Peer Link
The third row closes the failure: when g1 notices its upstream link has broken and fails over,
all forty of the forty flows reach, and the black hole drops to 0. The leaves still have not
heard anything; g1 takes their packet and hands it to its peer.
The cost shows up in two columns at once. Hops rise from 120 to 160 — one extra hop per flow. And the peer link column rises from 0 to 40: the link set up for control traffic carries all of the user traffic.
The bottom table shows how this cost dissolves. As the number of leaves that have heard climbs
from zero to nine, the reached count in the g1 failed over column stays fixed at 40; what
changes is hops falling from 160 to 120, and peer-link crossings falling from 40 to
0. The failover window does not lose packets; it routes them over the peer link, and as the
window closes, that load lifts.
The left column shows the same window without failover: as the number of leaves that have heard rises, reached flows climb linearly from 0 to 40, and black holes fall from 40 to 0. Even with eight leaves already having heard, 6 flows still go to the dead gateway.
Partial Information Produces a Loop Again
The last row pays off the course’s second claim for a second time. When g1 and g2 each
believe the other active, all forty of the forty flows enter a loop: black holes are 0,
reached is 0.
This situation is not contrived. As g1 was going down, it received the information “my peer is
now active”; g2, when g1 came back, received the information “my peer has the higher
priority.” Each heard something true, but at a different moment. What is wrong is what the
two say together.
Two numbers must be compared. In the regime with no information at all (nobody heard), forty packets fall into a black hole, 40 hops are spent, and the peer link carries 0 crossings: the failure is cheap and unmistakable. In the regime with partial information, forty packets enter a loop, 120 hops are spent, and the peer link carries 80 crossings — two crossings per flow. The loop is three times as expensive as the black hole, and it does not look like a black hole — it looks like congestion: the administrator does not see an idle link, but a full peer link.
The third claim also reads here. The leaves behind the forty flows that entered the loop were
split into two groups; five sent to g1, four to g2. Which group a flow belonged to made no
difference — all forty entered the loop. The leaf’s table was correct, and its packet still
died. No single device can put a packet into a loop by itself; a loop is the joint product of
the pair.
The sixth row carries a warning. When both believe themselves active — that is, when the virtual address answers from two devices at once — the fate table looks spotless: 40 reached, 0 loop, 0 black hole, 120 hops. This measurement cannot see the split-brain, because the packets genuinely arrive. Where it becomes visible is in a different table: when the same address is heard from two ports, the MAC address table oscillates between two records. That table is the subject of the MAC Address Tables lesson and is not repeated here.
The Counted Surfaces and Their Narrowing
This section writes how the failures look and how they are narrowed; it does not give a procedure. The attack side belongs to the Wireless Networks and Network Security course.
First surface — the length of the window. In the measurement, without failover, even with eight leaves already having heard, 6 flows still die. Narrowing: shortening the advertisement interval and the dead timer narrows the window; its cost is that a transient glitch can trigger an unnecessary failover. A second narrowing is precomputing the backup path: the table is not computed at the moment of failover, it is kept ready.
Second surface — a gateway that answers but cannot forward. In the measurement, 40 black holes and one hop per flow. Narrowing: upstream link tracking — the gateway’s priority is tied to its upstream link’s state; when the link drops, priority drops and the peer takes over. The measurement gives the full gain of this narrowing: black holes fall from 40 to 0, at the cost of the peer link carrying 40 crossings.
Third surface — two gateways each believing the other active. In the measurement, 40 loops and 80 crossings on the peer link. Narrowing: ranking priorities strictly and unequally, and turning off preemption — if the returning device does not reclaim the address, a two-sided standoff never forms. The hop limit also terminates the loop, but the limit is a ceiling, not a fix: its cost stands in the measurement at 120 hops.
Fourth surface — the peer link being singular. The peer link carries both the advertisements and, during failover, all of the user traffic; in the measurement, this load is 40 crossings. When the link breaks, the two devices cannot see each other, and a split-brain is born. Narrowing: making the peer link itself redundant and confirming the peer’s health over a second path — one link’s silence is not treated as proof of death.
Fifth surface — advertisements accepted without verification. A failover advertisement is a claim; without something to verify the claim, which device takes over the gateway is left to whoever announces it. Narrowing: tying advertisements to authentication and accepting them only on the ports where the gateway pair actually sits.
Summary
- Gateway redundancy puts two devices behind a single virtual address; the host knows the address, not the devices, and can only ask “is the address answering?”
- When the upstream link breaks and nobody has heard, all forty of the forty flows fall into a black hole one hop later: answering is not forwarding.
- When the gateway fails over, black holes fall to 0, but hops rise from 120 to 160 and peer-link crossings from 0 to 40; both recede as the number of leaves that have heard grows.
- When two gateways each believe the other active, all forty flows enter a loop: 120 hops and 80 crossings on the peer link — three times the no-information regime, and it looks like congestion.
- With leaves split between sending to one gateway and the other, all forty still entered the loop; a loop is the pair’s joint product, and when both believe themselves active, the measurement sees nothing wrong.
Next Step
The three lessons so far measured topology as a graph: nodes, links, hops, rounds. Table row count appeared as a column in every measurement, and every time it came out of the topology — 272 in the three-layer design, 468 in the four-spine design. But there is a second thing that determines row count, and it has nothing to do with topology: how destinations are named. On the same topology, a plan that distributes addresses by a scheme and a plan that distributes them by creation order fill the same table with a different number of rows. The next lesson counts that difference and closes the course.
To keep your progress and take notes, Log in
My notes
Log in to take notes.