Part Three

The
Network

Parts I and II built one machine: sand to transistor, kernel to language. Part III steps off the motherboard. The bit that a voltage carries inside a CPU is, at a different scale, the bit that crosses a continent, as light in glass. We follow the wire out of the chip and into the world: the physics of the substrate, the architectural decision to send packets rather than circuits, the protocol that pretends a noisy network is reliable, the conventions that make a "website" work, and the language that runs in every browser on Earth. By the end you will be able to read every layer of an HTTPS request from voltage to the page in the tab.

CHAPTER 08
The Wire
Physics of the substrate: copper, fibre, radio. Shannon's information theory. Encoding. Ethernet. The OSI and TCP/IP layer models that organise everything above.
CHAPTER 09
Packets: How the Internet Decided to Work
Circuit-switching vs packet-switching. ARPANET 1969. The IP header byte-by-byte. BGP and the planet-scale graph nobody owns. IPv4 → IPv6. Spoofing and hijacking.
CHAPTER 10
TCP: The Problem of Reliability
Reliability built on top of an unreliable network. The three-way handshake. Flow and congestion control as control theory. CUBIC, BBR, QUIC. SYN floods.
CHAPTER 11
HTTP, DNS, TLS: The Web
Berners-Lee 1989. What "a website" is at the protocol level. DNS as the internet's phone book. TLS handshake step-by-step. HTTP/2, HTTP/3, QUIC.
CHAPTER 12
JavaScript: The Language That Shouldn't Have Worked
Brendan Eich's ten days. The event loop. Node.js. The DOM. Web security: same-origin, XSS, CSRF, CSP.
Chapter 08

The
Wire

Copper, fibre and radio carry the signal out of the chip and into the world. The mathematics of how much information any of them can carry. The encodings that turn ones and zeros into wave shapes. The local-network protocols that share the substrate. And the layer model that lets every conversation about networking refer back to a single picture.

TopicsSubstrates · Shannon · encoding · Ethernet · OSI
Era covered1948 → present
Position fix51.51°N 0.09°W · 0 nm run
Chapter 08 hero · The Wire CPU continent application transport network physical · wire each layer rides on the one below and all of it starts as a signal on a wire
01 · Bridge from Part I

The bit in a transistor is the bit across a continent.

Parts I and II stayed inside one machine. Part III leaves it. The same bit that switched a transistor in Chapter 1 now travels: across the motherboard as a voltage step, out a network interface, down a cable, across an ocean as light in glass, into another machine on the other side of the planet. The bit is unchanged; only what carries it changes. What grows is the discipline required to keep the signal intact over that distance.

A wire, electrically, is a conductor with two ends. Push voltage in at one; some attenuated, slightly delayed version of that voltage shows up at the other. The delay is a function of length and cable type: about 5 ns per metre on copper, about the same on fibre. The attenuation is a function of frequency, distance, and material. The noise added along the way is a function of everything nearby: lightning, fluorescent lights, other wires. A one-metre USB cable inside a desk hides all of this. A two-thousand-kilometre undersea cable cannot.

How do you keep a signal readable after a kilometre of copper, a hundred kilometres of fibre, or a radio link through a forest in the rain? That is the problem this chapter works on, and the rest of Part III is built on top of the answer.

Fig 8.1 · One signal, two scales
Fig 8.1 · One signal, two scales Two panels. Inside a chip, a pulse crosses about a micrometre of copper wiring from one gate to the next and arrives unchanged. Under the ocean, a pulse of light crosses about 5,600 km of glass fibre from New York to London, taking about 28 milliseconds; it fades as it travels and is restored at each repeater along the way, arriving weakened and noisy but still readable. The two distances are more than twelve orders of magnitude apart. INSIDE A CHIP gate gate ≈ 1 micrometre of copper arrives unchanged UNDER THE OCEAN New York London repeaters ≈ 5,600 km of glass · ≈ 28 ms fades, is restored, arrives noisy but readable

A bit crossing a micrometre of chip wiring and a bit crossing 5,600 km of ocean are the same thing at scales a trillion times apart: a signal guided along a line, in copper or in glass. On the chip it arrives unchanged; under the sea it fades with distance, and repeaters every 50 to 100 km restore it.

How it works
  1. The speed. Light in fibre travels at about two-thirds of its speed in vacuum, so New York to London takes about 28 ms one way.
  2. The repeaters. Modern undersea repeaters amplify the light directly, in erbium-doped fibre, without turning it back into electricity.
  3. The voltage. Processor cores run at around 1 V; the older 3.3 V and 5 V levels survive only at the edges of boards and chips.
02 · Three substrates

Electrons in copper, photons in glass, waves through air.

All networking happens over one of three substrates: copper, fibre and wireless. They look very different but do the same job: transport bits as variations in some physical quantity that a receiver at the other end can measure.

Copper is the original. The signal is the voltage difference between two wires twisted around each other (a twisted pair), and the receiver reads only that difference. Interference from outside induces almost the same voltage on both wires at once; subtract one from the other and the shared noise cancels, while the signal, which is opposite on the two wires, survives. The twist keeps neither wire systematically closer to any noise source, so the two stay balanced along the whole run. Copper is cheap and easy to connect, but loses signal fast: at the ~100 MHz frequencies modern Ethernet uses, about 20 dB per hundred metres (decibels count by factors of ten: every 10 dB leaves a tenth of the power, so 20 dB leaves a hundredth), which limits a single copper run to roughly 100 metres before a switch or repeater is required. Almost every cable inside a house or office is copper.

Fibre is glass. The signal is light, switched on and off to carry the bits. It travels down a hair-thin glass core wrapped in a cladding of glass with a slightly lower refractive index, which reflects light meeting it at a shallow angle back into the core (total internal reflection), so the light stays trapped all the way to the receiver. The loss is far lower: modern fibres lose about 0.2 dB per kilometre, so repeaters can sit tens of kilometres apart (50 to 100 km on an undersea cable). Every undersea cable, every long-distance internet trunk, every data-centre backbone is fibre. The first transatlantic fibre cable, TAT-8, went into service in 1988 and carried 280 megabits per second.

Wireless needs no cable at all: the bits are variations in a radio wave travelling through space. Wi-Fi runs at 2.4, 5 and 6 GHz; cellular from below 1 GHz to tens of GHz; satellite up to tens of GHz. The trade: no wire to install, but every receiver in range hears every transmission, the medium is shared with every microwave oven and every Bluetooth device, and atmospheric absorption is a factor at the higher frequencies. The mathematics of how to share that single medium efficiently is the bulk of cellular and Wi-Fi design.

The substrate is the first attack surface. Copper wires emit small amounts of electromagnetic radiation that a receiver nearby can turn back into the original signal. This is the basis of TEMPEST, the US codename for standards, dating to the 1960s, for shielding sensitive equipment against it. (Van Eck showed in 1985 that the picture on an unshielded monitor could be rebuilt from its emissions across the street; a modern software radio does the same to a leaky cable from across a room.) Optical fibres are harder but not immune: bending a fibre slightly leaks a small fraction of its light through the cladding, and the Snowden documents in 2013 revealed that the NSA and GCHQ had been systematically tapping undersea fibre cables at landing stations for years. Wireless is hardest of all to secure. The whole story of Wi-Fi security, from WEP (Wired Equivalent Privacy, 1997, broken by 2001) through WPA (2003, broken 2008) and WPA2 (2004, broken 2017 by the KRACK attack) to WPA3 (2018; the flaws found in it in 2019 were patched), is the engineering response to the fact that on a radio link, the attacker is always already in the room.

Fig 8.2 · Three substrates, one job
Fig 8.2 · Three substrates, one job One request's path from a laptop to a data centre across the ocean. From laptop to router it travels as radio, Wi-Fi at 2.4, 5 or 6 GHz through shared air. From router to internet provider it travels on copper (a telephone or cable-TV line, a few kilometres) or on fibre. From provider to data centre it travels as light in glass fibre, losing about 0.2 dB per km. Each hop encodes the bits its own way. A white dot, keyed as one request, travels the whole path. laptop = one request, hop by hop router provider data centre radio Wi-Fi, through shared air copper phone line, a few km glass loses about 0.2 dB per km

A request from a laptop to a distant server crosses all three substrates: radio to the router, copper or fibre to the internet provider, and glass across the ocean. Each hop encodes the bits its own way, and the programs above see none of it.

How it works
  1. Copper. Cheap and easy to connect, but it loses signal quickly at high frequencies: Cat 6 loses about 20 dB per 100 m at 100 MHz, so Ethernet runs stop at 100 m.
  2. Glass. At 1550 nm fibre loses about 0.2 dB per km, so light travels tens of kilometres between amplifiers.
  3. Radio. It shares the air with every neighbour; cellular networks use bands from below 1 GHz to tens of GHz, Wi-Fi 2.4, 5 and 6 GHz.
03 · Shannon's information theory

1948. Bell Labs again. The number behind every wire.

In July and October of 1948, Claude Shannon, a thirty-two-year-old mathematician at Bell Labs, published "A Mathematical Theory of Communication" in two parts of the Bell System Technical Journal. Before this paper, "amount of information" was close to a metaphor (Hartley had only counted equally likely messages, in 1928). After it, information had units (bits) and a formula (entropy). And Shannon proved a result that still defines every modern network: every channel has a maximum bit rate, you can send reliably below it, and you cannot send reliably above it.

Shannon is one of the strangest figures in twentieth-century science. At twenty-one he wrote the 1937 master's thesis, met in Chapter 2, that put Boole's algebra into relay circuits; Howard Gardner later called it "possibly the most important, and also the most famous, master's thesis of the century."

He stayed strange. At Bell Labs, where he wrote the information theory paper on his own (the whole field sprang from one head, in one paper, more or less complete), he was known for riding a unicycle through the corridors while juggling. In 1950 he built Theseus, an electromechanical mouse that could solve a maze and remember the solution: arguably the first artificial learning machine. The papers he wrote in his "spare time" included the first paper on computer chess, the first paper on cryptography as information theory (classified during the war, declassified 1949), and a theory of juggling that reduced the art to an equation relating hands, balls, and air time. Alzheimer's took him slowly through the 1990s; he died in 2001, having given the mathematics to a networked world he could no longer quite see.

The key definition in his 1948 paper is entropy (named after the physics quantity it resembles). It measures how much surprise a source of symbols carries: on average, the fewest well-chosen yes-or-no questions needed to learn each symbol it sends. A fair coin needs one question per flip; a coin that always lands heads needs none, because you already know the answer. For a source whose symbols come with probabilities p₁, p₂, …, pₙ, the entropy is:

Shannon entropy

H = −Σ pᵢ log₂(pᵢ)

Measured in bits per symbol. Each term is the questions one symbol costs: a symbol with chance ⅛ is pinned down by three halving questions (log₂ 8 = −log₂ ⅛), and entropy averages those costs, weighted by how often each symbol comes. The fair coin (½, ½) gives H = 1, the most a two-sided source can carry; the always-heads coin (1, 0) gives 0. A coin that lands heads nine times in ten (0.9, 0.1) gives H ≈ 0.47 bits: if you code several flips at a time, with short codes for the likely runs and longer ones for the rare runs, a long run of them fits in about 0.47 bits per flip. This number is also the shortest average code length any lossless compression scheme can achieve on long messages: a source cannot be compressed below its entropy. Every lossless compressor, ZIP and PNG included, is judged by how close it gets to that floor.

Fig 8.3 · Entropy of a binary source as p varies
Fig 8.3 · Entropy of a binary source as p varies The entropy of a binary source, in bits per symbol, plotted against the probability p of a 1. It is 0 at p = 0 and p = 1, where every symbol is predictable, rises to 1 bit for a fair coin at p = 0.5, and is symmetric about it. A source that gives 1 nine times in ten carries about 0.47 bits per symbol, the least any lossless code can use for it. 0 0.5 1 p, chance of a 1 0 1 bits fair coin: 1 bit 90/10: 0.47 0.9

Entropy of a two-symbol source against p, the chance of a 1: 0 when every symbol is predictable, 1 bit for the fair coin, symmetric about it. A 90/10 source is marked at 0.47 bits, the least any lossless code can average for it.

How it works
  1. The formula. H(p) = −p log₂ p − (1 − p) log₂(1 − p): the average surprise, in bits, of one symbol.
  2. Symmetry. A 30/70 source is exactly as uncertain as a 70/30 one; the curve does not care which side is likelier.
  3. Reaching the floor. Arithmetic coding comes within a few percent of H for long messages; coding bit by bit cannot go below 1 bit per symbol.

Shannon's second result, the noisy channel coding theorem, put a number on what a wire can carry. Given a channel of bandwidth B hertz (the width of the band of frequencies it passes, not its bit rate) and signal-to-noise ratio SNR (signal power divided by noise power, a plain ratio: 30 dB is 1,000), the maximum reliable bit rate, the channel capacity, is:

Shannon–Hartley channel capacity

C = B · log₂(1 + SNR)

The shape of the formula can be read straight off the physics. A receiver can only tell two signal levels apart if they differ by more than the noise blurs them: with signal power S and noise power N (a voltage grows as the square root of the power it carries), the received range spans about √(S+N) and the blur is about √N, so the number of reliably distinguishable levels is their ratio: √(1+SNR). A pulse carrying one of those levels delivers log₂ of that many bits: ½ · log₂(1+SNR). And a channel of bandwidth B can carry at most 2B independent pulses per second, a limit Harry Nyquist had derived in 1928. Multiply the two, and the halves cancel: C = B · log₂(1+SNR). Bandwidth buys pulses per second; signal-to-noise buys bits per pulse; capacity is their product. Even the +1 earns its place, because the receiver's measuring range contains the noise as well as the signal.

If you transmit at rate R < C, there exists a coding scheme (a way of adding redundant bits so the receiver can find and repair what the noise damaged) that achieves arbitrarily low error rate. If R > C, errors are inevitable, no matter how clever the coding. The proof is non-constructive: Shannon proved good codes exist, decades before anyone knew how to build them. Modern error-correcting codes (LDPC, Polar, Turbo) approach within a fraction of a dB of Shannon's limit.

Fig 8.4 · Channel capacity as a function of SNR
Fig 8.4 · Channel capacity as a function of SNR Shannon's limit, in bits per second per hertz of bandwidth, against signal-to-noise ratio in decibels: log₂(1 + SNR). Above the curve no code can transmit without errors. Wi-Fi 6 at about 20 dB is allowed about 6.7 bits per hertz. A phone line at about 30 dB is allowed about 10, which over the 3.43 kHz band V.34 modems used is about 34 kbps; they reached 33.6 kbps, 9.8 bits per hertz, just under the curve. no code can go here 0 10 20 30 40 SNR, dB 0 5 10 bits per hertz of bandwidth Wi-Fi 6 · 6.7 V.34 9.8 phone line · 10

Shannon's limit, the curve: a channel of bandwidth B carries at most B · log₂(1 + SNR) bits per second, and above it errors cannot be avoided. A phone line at 30 dB (signal 1,000 times the noise) allows about 10 bits per hertz, about 34 kbps over the 3.43 kHz band V.34 modems use; they reached 33.6 kbps.

How it works
  1. Reading it. At high SNR each doubling of signal power (+3 dB) buys about one more bit per hertz; doubling the bandwidth doubles the capacity outright.
  2. Wi-Fi 6. At about 20 dB the limit is 6.7 bits per hertz; over an 80 MHz channel that is a few hundred Mbps per stream.
  3. 56k modems. They beat 33.6k only because the downstream path stayed digital until the last stretch of phone line, dodging most of the noise.
04 · Encoding

How a one-or-zero rides on a continuous wave.

Knowing the channel can carry ten million bits per second tells you nothing about how to represent those bits as voltage, light, or radio. Encoding is the layer that maps abstract ones and zeros onto physical waveforms the receiver can decode. Different encodings exist because they trade different properties: clock recovery, bandwidth efficiency, robustness to noise.

The most naive encoding is NRZ (Non-Return to Zero). High voltage means 1; low voltage means 0. The signal sits at one of two levels and changes only when a bit differs from the one before it. It is dead simple, with one fatal weakness: a long run of identical bits looks like a flat line. The receiver, reading bits at some clock rate, has no way to know when one bit ends and the next begins. Drift even slightly out of sync and the receiver counts one bit too many or too few, so every bit after it lands one place out.

Manchester encoding solves the synchronisation problem by embedding the clock in the data. Every bit is split into two halves; a transition in the middle carries the bit value (in Ethernet's convention, rising for a 1 and falling for a 0). There is a transition in every bit period, no matter what the data is. The receiver locks onto those transitions and stays in sync indefinitely. The cost is double bandwidth: every bit needs two half-bit slots. The first standard Ethernet (Section 05) used Manchester encoding because it could not afford to lose synchronisation on a long run of zeros.

Fig 8.5 · Manchester: every bit is a transition
Fig 8.5 · Manchester: every bit is a transition The bits 1 0 1 1 0 0 1 in Manchester code, as Ethernet's IEEE 802.3 standard defines it: each bit period has a transition in its middle, rising for a 1 and falling for a 0. Between two equal bits the line also changes at the boundary to get ready. A receiver reads the direction of each mid-bit edge and recovers 1 0 1 1 0 0 1, and because there is an edge in every bit, it also recovers the clock. bits 1 0 1 1 0 0 1 hi lo 1 0 1 1 0 0 1 read rising mid-bit edge = 1, falling = 0

The bits 1 0 1 1 0 0 1 in Manchester code: each mid-bit edge gives the bit and keeps the receiver's clock in step.

How it works
  1. Two conventions. Ethernet (IEEE 802.3) uses rising for 1; the older G. E. Thomas convention is the reverse. Either works if both ends agree.
  2. The bandwidth. 10 Mbps Ethernet carries up to 20 million signal changes per second, which is why the next speed, 100 Mbps, moved to more efficient line codes.
  3. Boundary edges. Between two equal bits the line must also change at the boundary, to be on the right side for the next mid-bit edge.
Fig 8.6 · Same bits, two waveforms
Fig 8.6 · Same bits, two waveforms The bits 1 0 0 0 0 1 1 sent two ways to a receiver whose clock runs fast. In NRZ the four zeros are one flat stretch with no edges, so the receiver cannot correct its clock and samples that stretch five times, reading 1 0 0 0 0 0 1 1: one zero too many. In Manchester every bit has an edge in its middle, so the receiver corrects its clock on every bit and reads 1 0 0 0 0 1 1 correctly. sent 1 0 0 0 0 1 1 NRZ 1 0 0 0 0 0 1 1 Manch. 1 0 0 0 0 1 1 read read receiver clock 20% fast, exaggerated

Sent to a receiver whose clock runs fast, NRZ's four zeros are one flat stretch with nothing to correct against, so the receiver reads five zeros. Manchester gives it an edge in every bit, so it corrects its clock each time and reads all seven bits correctly.

How it works
  1. Real drift. Real clocks differ by parts per million, not 20%, but over a long enough run of identical bits the error adds up the same way.
  2. The compromise. Modern links use codes such as 8b/10b and 64b/66b, which keep NRZ's simplicity but bound the longest flat run: 8b/10b never repeats a bit more than five times, and 64b/66b guarantees an edge in every 66-bit block.
  3. NRZ. Non-return-to-zero simply holds the level for each bit: high for 1, low for 0.

Radio, DSL and cable modems cannot send bare on-and-off levels like those of Fig 8.6. The bits ride instead on a carrier wave, a steady high-frequency wave the medium carries well. Modulation changes one property of the carrier in step with the data: its size (amplitude, AM), how fast it oscillates (frequency, FM), or where each cycle starts (phase, PSK). QAM (Quadrature Amplitude Modulation) changes size and phase together, so each symbol, one short stretch of the wave, carries several bits.

Fig 8.7 · Three ways to ride bits on a carrier
Fig 8.7 · Three ways to ride bits on a carrier The bits 1 0 1 0 carried on a sine wave three ways. Amplitude modulation makes the wave big for 1 and small for 0. Frequency modulation makes it faster for 1 and slower for 0. Phase shift keying keeps size and speed but flips the wave, so each 1 starts going up and each 0 starts going down. A receiver reads each bit from whichever property the scheme changes. bits 1 0 1 0 AM amplitude big small big small FM frequency fast slow fast slow PSK phase starts: up down up down

The bits 1 0 1 0 carried three ways: with each bit the wave's size, its speed or its starting direction changes. Wi-Fi and cable modems change size and phase together (QAM), packing many bits into each symbol: up to 10 in Wi-Fi 6, but only on a far cleaner signal than the 20 dB point in Fig 8.4.

How it works
  1. Noise. Interference mostly disturbs amplitude, so FM and PSK resist it better; Edwin Armstrong patented FM in 1933 for exactly that reason.
  2. QAM. 16 combinations of size and phase carry 4 bits per symbol; Wi-Fi 6 uses 1024 combinations (10 bits) and Wi-Fi 7 uses 4096 (12 bits) when the signal is clean, dropping to fewer as it degrades.
05 · Local networks

Metcalfe, 1973: many computers, one cable.

In 1973, Bob Metcalfe at Xerox PARC invented Ethernet, carrying onto a wire an idea from a radio network in Hawaii. The rule is plain: any number of computers share one cable; they all listen; whenever no one else is talking, anyone can send; if two start at the same time and the signals collide, both detect it, both back off for a random interval, and both try again. That rule is Carrier Sense, Multiple Access, with Collision Detection: CSMA/CD. In ever faster versions, Ethernet became the dominant local-area networking technology on the planet.

Metcalfe's Harvard PhD thesis on packet networking had been rejected (the committee felt it lacked theoretical depth), and the rejection sent him into a foul mood and onto a flight to Hawaii, where he had heard about a packet-radio network called ALOHAnet built by Norm Abramson at the University of Hawaii. ALOHAnet let the islands' campuses share one radio frequency by transmitting whenever they had data and retransmitting after random delays when collisions occurred. Metcalfe spent weeks there modelling the system, realised Abramson had badly underestimated how much of the channel's time could carry data with smarter timing, wrote the corrections into a revised thesis, and got his doctorate. Ethernet was the same insight on a wire, improved with carrier sensing ("listen before transmit").

The Xerox PARC of 1973 held an unusual concentration of computing talent. Down the hall from Metcalfe, Chuck Thacker and Butler Lampson were building the Alto, a personal computer with a screen that drew pictures (the machine that later showed Steve Jobs what a graphical interface could be), and Gary Starkweather had brought the laser printer to life. Metcalfe's job was to network these machines together. His first Ethernet memo, dated May 22, 1973 and full of hand-drawn diagrams, shows the architecture: many machines tapped onto one shared cable, the "ether".

PARC's original Ethernet ran at 2.94 megabits per second; the standardised 10BASE5 that spread through the industry ran at 10, over a single thick coaxial cable up to 500 metres long: the "thicknet" of the 1980 DIX standard (DEC, Intel and Xerox). By the 1990s, switches replaced the shared cable. Each device got its own dedicated cable to the switch; with no one else on the wire, collisions stopped happening, and the collision-detection machinery now mostly sits unused. But every Ethernet frame (the block of bits sent in one go) still starts with a header designed for that shared cable, which names sender and receiver by 48-bit MAC address (below).

"Members and affiliates of the Intergalactic Computer Network."

J.C.R. Licklider, ARPA memo salutation, 1963
Fig 8.8 · Two stations collide; both back off
Fig 8.8 · Two stations collide; both back off Two stations, A and B, at the ends of a shared cable. A starts sending; B, which has not yet heard A, starts too. The two signals meet and overlap on the wire: a collision. Each station hears it, sends a short jam signal and stops. After a first collision each picks 0 or 1 slot at random: A picks 0 and sends again, B picks 1, finds the line busy and waits, then sends once A has finished. A B collision A: wait 0 slotsB: wait 1 slot line busy: B waits A sends; B has not heard it yet the signals overlap: collision both hear it, send a jam, stop each picks 0 or 1 slot at random A goes first; B defers then B sends after the k-th collision: 0 to 2ᵏ − 1 slots

Two stations hear a quiet cable and start at almost the same moment; their signals collide, both notice, send a short jamming signal and stop. Each then waits a random number of slots (the time a signal takes to cross the cable and back), so one goes first and the other finds the line busy.

How it works
  1. The backoff. In truncated binary exponential backoff, after the k-th collision in a row a station waits a random 0 to 2k − 1 slot times (k stops growing at 10), and gives up after 16 tries.
  2. Why random. If both waited the same time they would collide again; each further collision doubles the range of waits, so the stations spread out as the line gets busier.
  3. Today. Wi-Fi uses the same random backoff but tries to avoid collisions rather than detect them.

Every Ethernet device has a MAC address (Media Access Control; no relation to Apple's Mac), a 48-bit identifier burned into the network card at manufacture. The first 24 bits are the OUI (Organizationally Unique Identifier), assigned to the manufacturer by the IEEE; the next 24 are the device-specific portion. So the address tells you who built the card: older Raspberry Pi boards begin B8:27:EB, newer ones DC:A6:32 and other prefixes; Apple's begin 00:03:93 or any of a few hundred other prefixes. Public databases map every OUI to its owner.

MAC addresses identify devices on the local link; IP addresses, which we'll meet in Chapter 9, identify them on the global network. ARP (Address Resolution Protocol) bridges the two. When a host wants to send a packet to 192.168.1.5 on its own local network, it broadcasts a question to every device on the link: "Who has 192.168.1.5? Tell me your MAC." The owner replies; the asker caches the answer for a while (seconds to minutes, depending on the system); subsequent packets to that IP go straight to the matching MAC.

Fig 8.9 · A MAC address, and the ARP cache that resolves it
Fig 8.9 · A MAC address, and the ARP cache that resolves it The MAC address B8:27:EB:9C:DA:C7, written in hexadecimal, is 48 bits in two halves joined by a colon: B8:27:EB names the maker, the Raspberry Pi Foundation, and 9C:DA:C7 names this one card. Below, a host at 192.168.1.10 wants to send to 192.168.1.5, which is not in its ARP cache. It broadcasts "who has 192.168.1.5?" to every host on the network; the owner replies with its MAC address, and the host adds the pair to its cache. MAC ADDRESS · 48 BITS IN HEXADECIMAL B8:27:EB maker: Raspberry Pi : 9C:DA:C7 this card ARP: IP ADDRESS TO MAC me 192.168.1.10 192.168.1.1 192.168.1.5 192.168.1.42 ARP CACHE ON 192.168.1.10 192.168.1.1 8C:85:90:1F:33:42 192.168.1.5B8:27:EB:9C:DA:C7 send to 192.168.1.5: not cached broadcast: who has 192.168.1.5? its owner replies with its MAC cached; the next frame goes direct

One MAC address split into maker and card, and the ARP exchange that finds it: broadcast the question, the owner replies, the answer is cached.

How it works
  1. Expiry. Entries age out after seconds to minutes, depending on the system, so a device that leaves or changes card is soon forgotten.
  2. No proof. ARP checks nothing: any host on the link can answer for any address. Chapter 15 returns to what that allows.
Fig 8.10 · Hub vs switch: broadcast vs learned
Fig 8.10 · Hub vs switch: broadcast vs learned Four hosts, A, B, C and D, on one central box. As a hub, it repeats a frame from A to B out of every port, so B, C and D all receive it. As a switch it starts with an empty table: a frame from A to B, whose port it does not know yet, is sent to every port, and the switch notes that A is on port 1. B's reply goes only to port 1, and the switch notes that B is on port 2. The next frame from A to B goes only to port 2. A port 1 B port 2 C port 3 D port 4 hub switch SWITCH TABLE A → port 1 B → port 2 hub:every port hears it B unknown:flood, learn A B replies:only to A; learn B A to B:only B's port

A hub repeats every frame out of every port. A switch learns which address sits behind which port by reading the source of each frame, floods only while a destination is still unknown, and after that sends each frame down the one right cable.

How it works
  1. Self-configuring. Nobody fills the table in: it builds itself from traffic, and entries expire when a device stops talking.
  2. No more collisions. With one device per switch port and both ends able to send at the same time (full duplex), collision detection never fires.
  3. Broadcasts. Frames addressed to everyone, such as ARP requests, still go out of every port.

MAC flooding: when a switch forgets it is a switch. A switch's MAC table has finite size, usually a few thousand entries on consumer hardware and tens of thousands on enterprise gear. An attacker with access to one port can rapidly send frames with a million different fabricated source MACs (the burned-in address is only a default: software writes each frame's source field, and the switch takes it on trust). The switch records each one, fills the table to capacity, and so can no longer keep or relearn the real entries as they expire; with real destinations unknown, it falls back to what it does when it does not know which port a destination MAC sits on: flood the frame out of every port. The switch has been downgraded to a hub, and every frame on the LAN is now visible to the attacker. This attack, called MAC flooding, was packaged into a point-and-shoot tool, macof, around 1999 and is the reason serious networks turn on port security, a cap on how many addresses each switch port will learn. The pattern recurs across networking: the polite, table-based, learning-by-listening designs of the 1990s assumed every participant played fair, and an attacker who declined to could turn cooperative infrastructure against its users.

06 · OSI & TCP/IP layer models

Why every textbook draws the same cake.

The layer model is why network engineers can talk about network problems precisely. Each layer does one thing only. Each layer is a customer of the layer below and a provider for the layer above. On the way out, each layer hands its data to the layer below, which wraps it in a header of its own; on arrival each layer removes its header and hands the rest up (Fig 8.11). Once you've drawn the layers, nearly every protocol falls into one slot, and every conversation about networking becomes "which layer is this happening at?"

The canonical reference is the OSI seven-layer model, named for Open Systems Interconnection and standardised by the International Organization for Standardization in 1984. Bottom to top: Physical, Data link, Network, Transport, Session, Presentation, Application. Each layer is named after the abstraction it provides. Physical moves bits over a medium. Data link moves frames between machines on the same local network, over cable, switch or radio link. Network moves packets across many networks joined together. Transport moves byte streams reliably or messages unreliably end to end. Session, Presentation, and Application are about what the bytes mean.

The TCP/IP four-layer model is the one that actually runs the internet. It collapses Physical and Data Link into a single "Link" layer (since the IP layer doesn't care which kind of link is below it), and collapses Session, Presentation, and Application into a single "Application" layer (since real applications usually handle their own session semantics). Even so, the OSI model remains the teaching reference because its separations are cleaner to teach.

Fig 8.11 · The OSI seven-layer cake
Fig 8.11 · The OSI seven-layer cake The seven layers of the OSI model, from Application at the top to Physical at the bottom, and a request passing down through them. At the application layer it is GET /. TLS, drawn here at the presentation layer, encrypts it. Transport adds a TCP header, Network an IP header, and Data link an Ethernet header in front and a checksum, FCS, behind. Session adds nothing. The physical layer sends the whole frame as a stream of bits on the wire. A key spells out Eth as the Ethernet header and FCS as the checksum. 7 Application GET / 6 Presentation TLS · GET / 5 Session TLS · GET / adds nothing 4 Transport TCP TLS 3 Network IP TCP TLS 2 Data link Eth IP TCP TLS FCS 1 Physical 0110 1001 0011… Eth: Ethernet header · FCS: checksum

Each layer serves the one above, and most add their own wrapping on the way down: TLS (the encryption of Chapter 11) around the request for a page, then a TCP header, an IP header, and an Ethernet header and checksum, until layer 1 sends it as signals.

How it works
  1. Examples. 7: HTTP, DNS, SSH. 6: TLS (its placement varies), compression. 5: session handling. 4: TCP, UDP. 3: IP, ICMP. 2: Ethernet, Wi-Fi. 1: copper, fibre, radio.
  2. On arrival. The receiver works upward, each layer removing its own header and handing the rest up, in the reverse order.
  3. The standard. ISO published the model in 1984 as ISO 7498; its protocols lost to TCP/IP, but its layer numbers stayed in engineers' vocabulary.
Fig 8.12 · OSI vs TCP/IP, side by side
Fig 8.12 · OSI vs TCP/IP, side by side The seven OSI layers beside the four TCP/IP layers. TCP/IP's Application layer, home of HTTP, DNS and SSH, covers OSI's Application, Presentation and Session. Its Transport layer, TCP and UDP, matches OSI Transport; its Internet layer, IP, matches OSI Network; and its Link layer, Ethernet and Wi-Fi, covers OSI's Data link and Physical. OSI TCP/IP 7 Application 6 Presentation 5 Session 4 Transport 3 Network 2 Data link 1 Physical Application HTTP, DNS, SSH Transport TCP, UDP Internet IP Link Ethernet, Wi-Fi

Read across: TCP/IP's Application covers OSI's top three layers and its Link the bottom two. OSI says where a feature belongs; TCP/IP describes what is on the wire.

How it works
  1. The names. TCP/IP's layers come from RFC 1122 (1989): link, internet, transport, application.
  2. Where TLS sits. It encrypts between the application and TCP, which is one reason neither model places it cleanly.

The seam to Chapter 9

Chapter 8 has been about the physical and link layers: how a single bit makes it across one hop. Chapter 9 climbs to the network layer. Every mechanism so far has moved bits between two devices on one wire, or a few on one Ethernet segment. The internet is billions of devices on tens of thousands of independently administered networks, none aware of most of the others, and a packet must find its way across all of them from any source to any destination. The idea that made this work was small, strange, and explicitly Cold War.

Chapter 09

Packets: How
the Internet
Decided to Work

The phone company spent a century building a network where every call got its own dedicated wire. ARPANET, in 1969, made a different bet: chop messages into pieces, label each piece with a destination, and let it find its own way through the graph. That single architectural decision (packets, not circuits) made the modern internet possible. It also made it possible for Pakistan's attempt to block YouTube at home to leak out and take the site off the air almost everywhere for about two hours.

TopicsPacket switching · IP · BGP · IPv4/v6 · spoofing & hijacks
Era covered1964 → present
Position fix47.60°N 30.10°W · 1,183 nm run
Chapter 09 hero · Packets: How the Internet Decided to Work SRC DST two packets · same source & destination · different paths
01 · The phone company's view

A century of dedicated wires.

Before the internet, the dominant way to carry a message across a continent was the telephone network, a vast machine for setting up dedicated end-to-end paths between exactly two parties. When you placed a call, a sequence of mechanical or electronic switches in central offices found a free wire from your handset, through trunk lines, to the handset on the other end. That entire path was reserved for your call, moment by moment, until you hung up. The model is called circuit switching, and for nearly a hundred years it had been the right way to carry a voice.

Circuit switching has real virtues. The path is established once, so the delay through the call never changes, and nothing can crowd it midway: the capacity is yours alone until you hang up. But it scales poorly for computers. A computer-to-computer conversation is not a continuous voice signal; it is a burst of activity, a long silence, another burst. Holding a dedicated circuit for a connection that is silent 99% of the time wastes 99% of the wire. And every new pair of correspondents needs its own end-to-end reservation, so the network has to know in advance how many simultaneous connections to plan for.

The alternative was packet switching: chop every message into small numbered chunks, write the destination on each chunk, and dump them into the network. The network's only job is to get each chunk closer to its destination. Different chunks may take different paths, arrive out of order, get duplicated, or get lost; the receiving end reassembles them and asks for retransmissions. The wires are shared by everyone: a moment after one sender's burst, the same bandwidth carries someone else's. Nothing has to be reserved or set up before traffic flows.

Fig 9.1 · One call, two architectures
Fig 9.1 · One call, two architectures Two ways to carry a conversation from A to B. Circuit switching reserves one path through the switches for the whole call; the voice flows along it, and during a silence the path stays reserved and unused. Packet switching shares a mesh of routers: three packets from A take three different paths, arrive in the order 2, 1, 3, and B puts them back in order. CIRCUIT: one path, reserved A B silence: still reserved nobody else may use it PACKETS: a shared mesh A B 1 2 3 arrived 2 1 3 in order: 1 2 3

The same call both ways: circuit switching holds one path through every silence, while three packets cross a shared mesh by three paths, arrive 2, 1, 3 and are put back in order by B. Computers talk in bursts, which is why the ARPANET was built on packets.

How it works
  1. Circuits. They guarantee capacity and steady delay, which suits a voice that never stops; the price is capacity left idle whenever it does.
  2. Packets. Each carries its destination, so a router can send it any way that works and route around a failure.
  3. Putting it back. Sequence numbers let the receiver reorder packets and notice gaps; Chapter 10's TCP does exactly that.
02 · ARPANET 1969

The first message, and the ideas behind it.

The first message on what became the internet was two letters long and broke the receiving computer. The date was 29 October 1969, the time 22:30 Pacific. Charley Kline, a student programmer in Leonard Kleinrock's lab at UCLA, sat at a terminal connected by a leased 50-kbps telephone line to the Stanford Research Institute, 560 kilometres up the coast. On the other end was Bill Duvall. The plan was to log in remotely: type LOGIN and see whether the host accepted it. Kline typed L. It arrived. He typed O. It arrived. He typed G, and the SRI machine, which had been trying to be helpful by autocompleting LOGIN when it saw the third letter, crashed under the surprise. Those two letters, LO, were the network's first packets. The system was back up an hour later, and the connection held for the rest of the evening. By December there were four nodes (UCLA, Stanford Research Institute, UC Santa Barbara, the University of Utah), and the network had a name: ARPANET.

The ground had been laid years earlier, by three people working independently. Paul Baran at the RAND Corporation, between 1960 and 1964, was working on the most pressing strategic problem of the age: how to keep the United States' command structure functioning during and after a Soviet nuclear strike. In October 1962, midway through that work, the Cuban Missile Crisis showed the world how close to thermonuclear war it was; the existing AT&T long-distance telephone network was a hierarchical tree of switching centres, and the loss of any few of those centres would have cut the country into pieces. Baran's RAND memoranda (eleven of them, eventually published as On Distributed Communications) argued for a different architecture entirely: a richly connected mesh, no central authority, messages broken into "message blocks" each routed independently, survivable in the face of arbitrary node loss. The Air Force liked the idea; AT&T, holding that a real network could not run without circuits, refused to build it, and the project stalled politically. The papers sat in RAND's library waiting for the right reader.

Donald Davies at the UK's National Physical Laboratory arrived at the same architecture in 1965, named it "packet switching" and built a small working network at NPL by 1969. Leonard Kleinrock at MIT, in his 1962 doctoral thesis, had worked out the mathematics of queues showing that traffic arriving in bursts, sharing one link, gets far more through than the same link split into reserved lines, one per conversation. Kleinrock then took a faculty position at UCLA. In 1966 Larry Roberts at ARPA (the US Advanced Research Projects Agency, not yet renamed DARPA) was tasked with building an experimental network. By 1967 he had read all three sets of work, and the design that emerged was unmistakably Baran's mesh, Davies's terminology and Kleinrock's math. The firm Bolt Beranek and Newman (BBN) built the IMPs (Interface Message Processors), the network's first routers. The "LO" message was the ARPANET's packet switching going live, though ARPA built it to let universities share expensive computers, not to survive a war.

Fig 9.2 · The first internet
Fig 9.2 · The first internet The ARPANET in December 1969: four computers, at UCLA, UC Santa Barbara, SRI in Menlo Park and the University of Utah, joined by leased lines. The first message went from UCLA to SRI on 29 October 1969: the letters L and O arrived, and on G the receiving system crashed. Below, how it grew: 15 nodes in 1971, across the Atlantic in 1973, the first TCP paper in 1974, 213 hosts in 1981, the switch to TCP/IP in 1983, and the ARPANET retired in 1990. DECEMBER 1969 SRI UC SantaBarbara UCLA Utah L O G SRI got: L O crash UCLA to SRI, 29 Oct 1969 typing LOGIN HOW IT GREW 1969 4 nodes (above) 1971 15 nodes 1973 across the Atlantic 1974 TCP first published 1981 213 hosts 1983 switch to TCP/IP 1990 ARPANET retired

In December 1969 the ARPANET was four computers on leased 50 kbps lines, each joined to the network by an IMP, a refrigerator-sized packet switch built by BBN. Below, how the network grew until its retirement in 1990.

How it works
  1. The IMPs. They were Honeywell DDP-516 minicomputers that did the packet switching, so the hosts did not have to.
  2. Flag day. On 1 January 1983 the ARPANET switched from its original protocol, NCP, to TCP/IP, the protocols the internet still runs.
Fig 9.3 · Baran 1964: three architectural shapes
Fig 9.3 · Baran 1964: three architectural shapes centralised 0 of 8 decentralised 8 of 11 distributed 9 of 9 the same kind of strike, three networks survivors that can reach another Paul Baran's three network shapes from 1964, each hit by the same kind of strike. The centralised star loses its centre, and none of its eight survivors can reach another. The decentralised network loses one of its three hubs: that hub's three nodes are cut off, and 8 of the 11 survivors stay connected. The distributed mesh loses three nodes, and all nine survivors can still reach each other by routing around the gaps.

Paul Baran's 1964 RAND memoranda compared three shapes under attack: a star dies with its centre, a set of hubs loses whole regions, and a mesh with no hubs routes around its losses. His third shape, packets finding their own way through redundant links, became the ARPANET's design, though survival was never its purpose.

How it works
  1. Redundancy. His studies found that each node needs only a few links, around three or four, for the mesh to survive heavy losses.
  2. Today. The internet is no pure mesh, with large exchange points and backbones, but routing still sends traffic around a failed link or router.
03 · IP, the Internet Protocol

The packet, byte by byte.

The first twenty bytes of every IP packet (inside the Ethernet frame of Chapter 8) are the IP header, a fixed-format arrangement of fields that every router on the path reads to figure out where the packet should go next, whether to fragment it and when to drop it. One field tells the receiver which higher-layer protocol the payload belongs to. Everything after the header is payload: what the sender actually wanted to deliver. The header is tiny by modern standards, designed in 1981 when every byte cost real money to transmit.

Fig 9.4 · The IPv4 header, byte by byte
Fig 9.4 · The IPv4 header, byte by byte A real 20-byte IPv4 header, laid out 32 bits to a row as in RFC 791: version 4, header length 5 words, type of service 0, total length 60 bytes; identification 7238, the flags, fragment offset 0; time to live 64, protocol 6 for TCP, header checksum 12849; source 192.0.2.10; destination 198.51.100.7. A router reads the destination to choose the next hop, lowers the time to live to 63, recomputes the checksum to 13105 and forwards the packet. 0 31 bits Version 4 IHL 5 ToS 0 Total length 60 Identification 7238 Flags010 Fragment offset 0 TTL Protocol 6 TCP Header checksum Source address 192.0.2.10 Destination address 198.51.100.7 64 63 12849 13105 payload: a TCP segment router: look up the destination decrement the time to live TTL changed, so recompute the checksum forward to the next hop

Twenty bytes in front of every IPv4 packet, laid out as RFC 791 set them in 1981. At each hop a router reads the destination to pick the next link, lowers the time to live by one and recomputes the checksum; the rest travels untouched.

How it works
  1. The small fields. IHL is the header length in 32-bit words, almost always 5; ToS once meant priority; today it marks a traffic class and congestion; Total length counts header and payload, at most 65,535 bytes. Flags are three bits: reserved, don't fragment, more fragments; 010 here sets don't fragment.
  2. Time to live. The TTL drops by one at every hop, so a packet caught in a routing loop eventually dies; at 0 the router drops it and reports back, which is how traceroute maps a path: probes with TTL 1, 2, 3 make each hop answer in turn. The header checksum is a number worked out from the header's bytes, so damage shows up; change the TTL and it changes too, so each hop recomputes it.

Every link has a largest packet it can carry, its maximum transmission unit (MTU), and a bigger packet cannot cross it in one piece. Ethernet's standard MTU is 1500 bytes. When a packet carrying 4000 bytes of data must traverse a 1500-byte link, the router (or sometimes the original sender) splits it into fragments, each a complete IP packet with the same Identification field but its own Fragment Offset, saying where in the original payload it sits, and a "More Fragments" flag in the Flags field. The receiving end reassembles. Fragmentation is generally avoided in modern networks.

Fig 9.5 · 4000 bytes of data, fragmented onto a 1500-byte link
Fig 9.5 · 4000 bytes of data, fragmented onto a 1500-byte link A packet carrying 4000 bytes of data, identification 42, meets a link whose largest packet is 1500 bytes. It is split into three fragments carrying 1480, 1480 and 1040 bytes, with fragment offsets 0, 185 and 370 (counted in 8-byte units) and the more-fragments flag set on all but the last. In a first run all three arrive and the receiver reassembles the packet. In a second run fragment 2 is lost, and since IP cannot ask for one fragment again, the receiver discards the whole packet. ORIGINAL · ID 42 4000 bytes of data link MTU 1500 bytes: split RECEIVER offset in 8-byte units 1480 B · moreoffset 0 1480 B · moreoffset 0 1480 B · moreoffset 185 1480 B · moreoffset 185 lost 1040 B · lastoffset 370 1040 B · lastoffset 370 all three in: packet rebuilt one missing: whole packet discarded

A packet carrying 4000 bytes of data meets a link that carries at most 1500 bytes, so it is cut into three fragments. Lose any one and the receiver must throw the whole packet away, since IP cannot ask for a single fragment again.

How it works
  1. The arithmetic. Each fragment needs its own 20-byte header, leaving 1480 bytes of data; 1480 / 8 = 185, so the second fragment starts at offset 185.
  2. Avoiding it. Path MTU discovery finds the smallest link on the route: the sender marks its packets "don't fragment" (the flag of Fig 9.4), so a router that cannot pass one drops it and tells the sender the limit, and the sender resends smaller packets.
  3. IPv6. Routers never fragment; only the sender may, after learning the path's limit.
04 · BGP & routing

The graph that nobody owns.

The internet is tens of thousands of independent networks run by different organisations, glued together by a single protocol that lets them announce routes to one another. That protocol is BGP, the Border Gateway Protocol. It was born in 1989, runs on every backbone router on the planet, and was designed with almost no security, a fact the internet still has not fully recovered from.

Each independent network is called an Autonomous System (AS) and gets a number: Cogent is AS 174, Google is AS 15169, your internet provider (ISP) has its own. ASes connect where their networks meet, and each link is a business deal: one network pays the other to carry its traffic (transit), or two swap traffic for free (peering). BGP is how an AS tells its neighbours: "I can reach these blocks of addresses; here is the path of ASes the traffic will take." A block is written as a prefix: 198.51.0.0/16 means every address that starts with 198.51 (the first 16 of the 32 bits, two of the four numbers), about 65,000 of them. A longer prefix, such as a /24, covers fewer addresses (256) and is called more specific. Each AS that hears the announcement adds itself to the path and re-announces to its own neighbours, propagating the route across the whole graph.

Fig 9.6 · BGP path-vector announcements
Fig 9.6 · BGP path-vector announcements Five autonomous systems. AS 65001 announces the prefix 198.51.0.0/16 with the path 65001. AS 65010 and AS 65020 each add their own number and pass it on, as 65010 65001 and 65020 65001. AS 65030 hears both equal-length paths, picks one by local policy, adds itself and announces 65030 65010 65001 to AS 65099. A copy announced back to AS 65001 carries its own number, so it is dropped as a loop. 65001 65010 65020 65030 65099 origin hears it 65030 65010 65001 AS NUMBERS · EACH ADDS ITSELF 165001 announces 198.51.0.0/16 (198.51.x.x) 2path grows: 65010 65001, 65020 65001 365030 picks one by policy, adds itself 4back at 65001: its own number, dropped

Each AS adds its number as the announcement spreads (steps 1 to 3). When a copy comes back to AS 65001 carrying its own number, it knows the route has looped and drops it (step 4).

How it works
  1. Choosing. Given several paths, an AS applies its own policy first, often business relationships; path length only breaks ties by default.
  2. AS numbers. 65001 to 65099 here are from the private range; real networks use public numbers, like the 174 and 15169 above.
  3. Scale. Every router at the internet's core holds a route to roughly a million IPv4 prefixes, learned this way.
Fig 9.7 · The internet's tier hierarchy
Fig 9.7 · The internet's tier hierarchy Three tiers of networks on each side. A packet leaves your ISP, a Tier 3 network, climbs to a regional ISP and then to a Tier 1 network, crosses to another Tier 1 network over a peering link for which neither pays, and comes down through a regional ISP to the server's host. Money flows up each side: your ISP pays the regional network for transit, and the regional network pays the Tier 1. Tier 1 Tier 1 regional ISP regional ISP your ISP server host Tier 2 Tier 3 Tier 1s peer, no payment pays pays pays pays

A packet climbs from your ISP through a regional network to a Tier 1, crosses to another Tier 1 by peering, and comes back down to the server. Money flows up on both sides: each network pays the one above it for transit, while the dozen or so Tier 1s exchange traffic with each other for free.

How it works
  1. Tier 1. A network that reaches the whole internet without paying anyone: Lumen, NTT, Arelion (formerly Telia Carrier), Tata, Cogent and a few more.
  2. Shortcuts. Large networks also peer directly at internet exchange points, and big content providers connect straight into ISPs, so many packets never reach Tier 1.
  3. No centre. The graph is held together by BGP announcements and commercial contracts; no authority runs it.
05 · IPv4 vs IPv6

The thirty-year migration that still isn't done.

IPv4 addresses are 32 bits long. That is 4,294,967,296 possible addresses, which sounded like a lot in 1981, though fewer are usable: whole ranges are reserved, and blocks are handed out in fixed sizes, so part of each often sits idle. The world ran out of fresh IPv4 allocations at the central pool of IANA, the body that hands out address blocks to the regional registries, on 3 February 2011, and at the regional registries over the years that followed. The successor is IPv6, defined in 1995. It uses 128-bit addresses, 296 times as many. Thirty years on, the migration is still incomplete, and structurally so: a v6-only machine and a v4-only machine cannot talk to each other directly, so nobody can drop the old protocol until nearly everyone else has adopted the new one. Depending on how you count, between 35% and 45% of internet traffic is IPv6 in 2025.

Fig 9.8 · IPv4 ran out, then CGNAT papered over it
Fig 9.8 · IPv4 ran out, then CGNAT papered over it Three homes with addresses in the shared 100.64.0.0/10 range sit behind a carrier-grade NAT that owns one public address, 203.0.113.5. When home A connects to a server, the NAT records 100.64.0.11 port 5000 as 203.0.113.5 port 40001; home B gets port 40002. A connection started from outside to 203.0.113.5 matches no entry in the table and is dropped. home A 100.64.0.11 home B 100.64.0.12 home C 100.64.0.13 CGNAT one IP server outsider TRANSLATION TABLE · 203.0.113.5 100.64.0.11:5000port 40001 100.64.0.12:5000port 40002 from outside: no entry, dropped

Many ISPs put customers behind carrier-grade NAT (CGNAT), network address translation at ISP scale: hundreds of homes share one public address, and a translation table gives each outgoing connection its own port number (a label that tells conversations apart, Chapter 10). A connection started from outside matches no entry and is dropped, so nothing can be hosted from behind it.

How it works
  1. The shared range. 100.64.0.0/10 was set aside in 2012 for exactly this layer. Every ISP reuses it inside its own network and never lets it onto the public internet, so it uses up no public addresses and never collides with the 192.168 addresses that each home router gives its own devices.
  2. The real fix. IPv6 gives every device its own public address; by 2025 close to half of Google's users reached it over IPv6.
Fig 9.9 · The shape of an IPv4 vs an IPv6 address
Fig 9.9 · The shape of an IPv4 vs an IPv6 address An IPv4 address, 192.0.2.42, is 32 bits written as four decimal octets; there are about 4.3 billion. An IPv6 address is 128 bits written as eight groups of four hexadecimal digits, 2001:0db8:85a3:0000:0000:8a2e:0370:7334; there are about 3.4 × 10^38. It may be shortened in two steps: drop each group's leading zeros, giving 2001:db8:85a3:0:0:8a2e:370:7334, then replace one run of zero groups with a double colon, giving 2001:db8:85a3::8a2e:370:7334. The first 64 bits name the network, the last 64 the device. IPv4 · 32 bits · about 4.3 billion 192 8 bits 0 8 bits 2 8 bits 42 8 bits IPv6 · 128 bits · about 3.4 × 1038 2001:0db8:85a3:0000:0000:8a2e:0370:7334 1 · eight 16-bit groups, base 16 (0–9, a–f) 2001:db8:85a3:0:0:8a2e:370:7334 2 · drop leading zeros 2001:db8:85a3::8a2e:370:7334 3 · one run of zeros becomes :: network · 64 bits device · 64 bits

IPv4 writes its 32 bits as four decimal octets; IPv6 writes 128 bits as eight hexadecimal groups, shortened by dropping leading zeros and collapsing one run of zero groups into ::. The first 64 bits name the network and the last 64 the device, a split the abundance makes affordable.

How it works
  1. Scale. 232 is about 4.3 billion, fewer than the people alive; 2128 is about 3.4 × 1038, so even every 64-bit subnet has room for 1.8 × 1019 devices.
  2. One :: only. It may appear once per address, or a reader could not tell how many zero groups each stood for.
  3. The price. Every router, operating system, firewall and networking library had to change before IPv6 could carry real traffic.
06 · Spoofing, hijacks, route leaks

Trust the source field at your peril.

The IP source address (Fig 9.4) is set by the sender and nothing in IP itself verifies it: the receiver sees whatever the sender wrote. This is IP spoofing, and it is built into the protocol. Most modern ISPs filter outgoing packets to stop their own customers from spoofing arbitrary sources (BCP 38, best current practice from 2000), but enforcement is uneven.

The Morris worm, 2 November 1988: the moment computer security became a discipline. Robert Tappan Morris, the Cornell graduate student of Chapter 3, whose father was the chief scientist at the NSA's National Computer Security Center, released a self-replicating program onto the young internet that exploited three vulnerabilities at once: a buffer overflow in the UNIX fingerd service, a debug feature accidentally left on in sendmail, and the trusted-host logins of rsh (remote login), which skipped the password for listed machines, helped along by guessable passwords. Each copy, on landing on a new host, replicated itself to nearby hosts. Morris had tried to avoid re-infecting compromised machines, but one time in seven it copied itself anyway, even onto a machine that said it was already infected, so machines were re-infected dozens of times until they collapsed under the load. Within hours, about six thousand machines (by the usual rough estimate, a tenth of the internet of the time) were unusable. Morris was traced within days. Within weeks of the worm's release, DARPA funded the Computer Emergency Response Team (CERT) at Carnegie Mellon, the first organisation tasked with coordinating response to internet security incidents. It became the model for the national CERTs that followed. The internet of 1988 had been built in an era of institutional trust; the Morris worm proved that era over.

Fig 9.10 · IP spoofing: a forged source field
Fig 9.10 · IP spoofing: a forged source field A packet's header has a source and a destination field, and the sender fills in both. A sender on the network 203.0.113.0/24 sends a packet to 192.0.2.10 with the source field set to 198.51.100.5, an address that is not its own. With no filter at the ISP's edge router it gets through, since routers forward on the destination only, and the reply goes to 198.51.100.5 instead of the sender. Then the edge router checks sources: a packet from the sender's own address 203.0.113.7 passes, and the packet with the forged source is dropped at the edge. source destination 192.0.2.10 198.51.100.5 203.0.113.7 198.51.100.5 forged genuine forged sender 203.0.113.x ISP edge router no filter checks source routes by destination 192.0.2.10 198.51.100.5 no filter: the forgery gets through the reply goes to 198.51.100.5 filter on: its own address passes forged source: dropped at the edge

The same forged packet twice: with no filter at the ISP's edge it gets through and any reply goes to the address it names; with the BCP 38 check on, the edge router drops it, because the source is not one of the customer's own addresses.

How it works
  1. One way only. A forger never sees the reply, so spoofing suits single packets that need no answer, either to hide who sent them or to aim the replies at a victim, as Chapter 10's SYN flood often does; holding a whole TCP conversation blind means guessing what the replies say, which Mitnick managed in 1994 (Chapter 10).
  2. Automating the check. Routers can apply BCP 38 automatically: strict unicast reverse path forwarding (uRPF) accepts a packet only if the route back to its source leaves through the interface the packet arrived on.
  3. Uneven deployment. The filter only works where each edge network applies it; measurement projects such as CAIDA's Spoofer still find networks that let forged packets out.

BGP can also be misled, by malice or by mistake. Each AS announces which prefixes it can reach, and nothing in the original protocol lets the hearer verify that the announcer owns them: an AS can announce anything and its neighbours will believe it. If the announcement is more specific than the legitimate one, the hijacker wins: routers nearly everywhere send the traffic its way, because forwarding always prefers the most specific route it knows. This is BGP hijacking: announcing addresses you do not hold, whether on purpose or by error. The canonical example happened on 24 February 2008.

Fig 9.11 · Pakistan/YouTube, 24 February 2008
Fig 9.11 · Pakistan/YouTube, 24 February 2008 YouTube's address block 208.65.152.0/22, 1,024 addresses, drawn as four /24s of 256 each, and traffic to 208.65.153.x. Before 18:47 UTC the only route is YouTube's /22 and the traffic reaches YouTube. At 18:47 Pakistan Telecom's announcement of 208.65.153.0/24, meant to block YouTube inside Pakistan and leaked worldwide by its upstream, is more specific, so routers everywhere send the traffic to Pakistan, where it is dropped. At 20:18 YouTube announces two /25s of 128 addresses each, more specific again, and wins the traffic back; at 21:01 the /24 is withdrawn. 208.65.152.0/22, YouTube's block .152 .153 .154 .155 /22 YouTube 1,024 addresses 256 /24 Pakistan Telecom 128128two /25s, YouTube YouTube Pakistan: dropped before 18:47 UTC: the /22 wins 18:47: the leaked /24 is more specific 20:18: two /25s win the traffic back 21:01: the /24 is withdrawn traffic to 208.65.153.x

Routers follow the most specific route they know, so Pakistan Telecom's /24, meant to block YouTube at home but leaked worldwide by its upstream PCCW, beat YouTube's own /22 almost everywhere. YouTube answered with two still more specific /25s; withdrawing the /24 ended the outage after about two hours.

How it works
  1. No malice. The block was a domestic order. Pakistan Telecom's mistake was announcing the route to its upstream PCCW; PCCW's was passing it on to the world without filtering it.
  2. Origin validation. With RPKI, a prefix holder signs a route origin authorisation (ROA) naming who may announce its prefixes and how specific they may be; a router that checks it drops a /24 like this one as invalid.
  3. Why it must be automatic. A new route spreads across the world's routers within minutes, faster than any operator can notice and react.
Fig 9.12 · A route leak: traffic takes the wrong door
Fig 9.12 · A route leak: traffic takes the wrong door Two large providers, P1 and P2, connected directly; a small customer network C buys transit from both. Traffic from a source behind P1 to a destination behind P2 normally crosses the direct P1 to P2 link. Then C mistakenly passes the routes it learned from P2 on to P1. P1 prefers routes learned from a paying customer, so the traffic now detours through C's two thin links, which cannot carry it. direct, wide P1 P2 C · small source destination leaked route normal: across the direct link C leaks P2's routes to P1 P1 prefers them: traffic through C

Customer C buys transit from P1 and P2; when it mistakenly passes P2's routes on to P1, P1 prefers them, since a paying customer's routes usually win, and traffic that crossed directly now squeezes through C's small links. That is a route leak: genuine routes reaching a neighbour that should never have heard them, with no attacker at all.

How it works
  1. The rule broken. A customer should pass its providers' routes only to its own customers, never to another provider; RFC 7908 (2016) catalogues the ways this goes wrong.
  2. Why it spreads. BGP trusts what neighbours announce, so P1 passes the leaked route on to everyone it talks to.
  3. Defences. Route filters, the routing registries, RPKI and the newer ASPA records all exist, and are only partly deployed.

The seam to Chapter 10

Chapter 9 has built the layer that gets a packet from any source to any destination: best-effort, possibly out-of-order, possibly lost. Chapter 10 builds reliability on top of that unreliability. The protocol it describes, TCP, is older than the web and still carries somewhere over half of all internet traffic.

Chapter 10

TCP: The
Problem of
Reliability

IP delivers packets best-effort: lossy, out-of-order, sometimes duplicated. For voice and DNS that's fine. UDP rides directly on top, and applications cope. For everything else (file transfer, web pages, long-lived connections), somebody has to make an unreliable network behave reliably. TCP is that somebody. Its mathematics is control theory, refined over half a century of engineering, and its handshake is the target of the SYN flood, one of the oldest denial-of-service attacks.

TopicsUDP · 3-way handshake · AIMD · CUBIC · BBR · QUIC · SYN flood
Era covered1974 → present
Position fix44.85°N 44.90°W · 1,819 nm run
Chapter 10 hero · TCP: The Problem of Reliability client countsfrom X server countsfrom Y SYN · let's connect, I count from X SYN-ACK · yes, I count from Y ACK · got it, we are connected three messages. one connection. SYN: synchronise · ACK: acknowledge the numbers let each side spot a lost packet and resend it
01 · UDP first

The simplest thing that could possibly work.

Before TCP and its machinery of reliability, there is the simplest thing you can do with IP: just send packets. No connection. No retransmission. No ordering. No way for the receiver to say slow down. That is UDP, the User Datagram Protocol: RFC 768, 1980, three pages long. Its eight-byte header adds little to IP beyond two port numbers, one naming the program at each end (port 53 is a DNS server's), so a datagram reaches the right program and not just the right machine. And yet UDP carries an enormous fraction of modern internet traffic (DNS, most video calls, most fast-paced games, every connection over QUIC, the newer transport of Section 05), because for these applications, the simplicity is the feature.

The trade is explicit. UDP says: I will hand your bytes to the IP layer; whether they arrive, in what order, possibly duplicated, is your problem. Most applications find this terrifying; a few find it liberating. A live phone call cannot wait for retransmission. A packet that arrives 200 milliseconds late is worse than a packet that didn't arrive at all (a lost packet costs only the ~20 milliseconds of audio it carried, a gap the ear glides over; a late one stalls the stream, and the stall is the hiccup you hear). A DNS lookup (asking which address a name such as example.com belongs to, Chapter 11) is a single small request and a single small response; TCP's three-message setup (Section 03) would add a full round trip before the question could even leave, doubling the time the lookup takes, for nothing.

Fig 10.1 · UDP: eight bytes, no state, no apologies
Fig 10.1 · UDP: eight bytes, no state, no apologies A UDP header is eight bytes: source port 51234, destination port 53 (DNS), length 37 bytes including the header, and a checksum. Then a sender sends three datagrams; the second is lost on the way. Nothing in UDP notices or resends it: the receiver simply has datagrams 1 and 3, and if anything is to be retried, the application must do it. UDP HEADER · 8 BYTES source port 51234 destination port 53 · DNS length 37 checksum optional in IPv4 payload: the DNS query sender receiver 1 2 3 has: 1 3 sender: no idea no resend: retrying is the application's job

Port 51234 asks port 53 a DNS question behind eight bytes of header. Datagram 2 is lost and nothing notices; retrying, if anyone does, is the application's job.

How it works
  1. The checksum. Optional over IPv4, required over IPv6.
  2. Building on it. QUIC, the transport under HTTP/3, runs on UDP and adds its own reliability and encryption on top.
02 · Why TCP

Four engineering goals, written 1974, still in force.

In 1974, Vint Cerf and Bob Kahn published "A Protocol for Packet Network Intercommunication" in the IEEE Transactions on Communications. Its design, the Transmission Control Program (TCP; the last word later became Protocol), was meant to do four things on top of an unreliable packet network: segment a long byte stream into manageable chunks, called segments, order them so the receiver sees them in sequence, ensure their reliable delivery despite arbitrary loss, and apply flow control so a fast sender does not overwhelm a slow receiver. Everything in the 1974 design serves one of those four goals; the one large later addition, congestion control, came in 1988 and is the subject of Section 04.

The architecture follows. Every byte of the application's stream is assigned a sequence number: a 32-bit counter, incremented for each byte, that lets either side say exactly which part of the stream a segment carries. The sender retains a buffer of sent-but-not-yet-acknowledged bytes; if no acknowledgement arrives within a timeout, it retransmits. The receiver buffers out-of-order arrivals and reassembles them in sequence-number order. Every acknowledgement also advertises a window (how many more bytes the receiver has buffer space to accept), and senders never have more than that many in flight (sent but not yet acknowledged). Each of these is simple on its own. Together, on a network that loses ten percent of its packets, they create the illusion of a reliable byte stream that loses nothing.

The cost of all this is state. On both client and server, every TCP connection keeps a kernel-level data structure called the TCB (Transmission Control Block) tracking sequence numbers, window sizes, retransmit timers, the path's measured round-trip time, the congestion-control state (Section 04), and a few dozen other values. A Linux server with a million open TCP connections holds a million TCBs in kernel memory.

03 · The three-way handshake

Three segments. Two synchronised counters. One conversation.

A TCP connection begins with the three-way handshake, three messages (TCP calls each one a segment): SYN (synchronise: here is my starting number), SYN-ACK (got yours; here is mine) and ACK (got yours). When the exchange is complete, both sides know each other's starting sequence numbers, both have allocated a TCB, and both are ready to stream. Why three? Two would leave the server's own starting counter unacknowledged, so the server would be streaming against a number it could never confirm the client had received. Three is exactly enough for each side to prove the other reachable and acknowledge the other's starting counter. A fourth is not needed, because the server's acknowledgement and its own starting number travel together in the SYN-ACK.

Fig 10.2 · The three-way handshake, in time
Fig 10.2 · The three-way handshake, in time Time runs downward between a client and a server. The client sends SYN with its initial sequence number, 4170, and moves from CLOSED to SYN_SENT. The server, listening, replies SYN-ACK with its own number, 9012, acknowledging 4171, and moves to SYN_RCVD (SYN received). The client replies ACK, acknowledging 9013; both sides are ESTABLISHED. One full round trip passes before the client's first byte of data can leave. client server CLOSED SYN_SENT ESTABLISHED LISTEN SYN_RCVD(SYN received) ESTABLISHED SYN · start 4170 SYN-ACK · start 9012 · ack 4171 ACK · ack 9013 first data oneroundtrip

The client sends SYN with its starting sequence number; the server answers with its own and acknowledges the client's plus one; the client acknowledges the server's plus one, and both now know both counters. That costs one full round trip before the first byte of data, the tax QUIC (Section 05) sets out to cut.

How it works
  1. Random starts. Each side picks its initial sequence number unpredictably, so an outsider cannot guess the numbers needed to inject into the connection.
  2. Plus one. An acknowledgement names the next byte expected, and SYN counts as one byte of sequence space, so the acknowledgements are 4171 and 9013.
  3. Data early. The third segment may already carry data, but nothing can leave before it.

Each end keeps its connection in one of eleven named states. A segment arriving, a call from the program or a timer running out moves it from one state to the next, by fixed rules: a state machine covering setup, data transfer and teardown. Two paths carry almost every connection. The side that connects and closes first walks CLOSED → SYN_SENT → ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED; the side that listens and closes second walks CLOSED → LISTEN → SYN_RCVD → ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED. Each side closes by sending a FIN (the segment that says I have nothing more to send). The eleventh state, CLOSING, and the remaining moves handle simultaneous opens and closes and the other partial failures the protocol was designed to survive.

Fig 10.3 · TCP's state machine
Fig 10.3 · TCP's state machine TCP's eleven connection states. A client goes from CLOSED to SYN_SENT when it connects, to ESTABLISHED when the SYN-ACK arrives, to FIN_WAIT_1 when it closes, FIN_WAIT_2 when its FIN is acknowledged, TIME_WAIT when the other side's FIN arrives, and back to CLOSED after waiting twice the maximum segment lifetime. A server goes from CLOSED to LISTEN, to SYN_RCVD (SYN received) when a SYN arrives, to ESTABLISHED on the ACK, to CLOSE_WAIT when the client's FIN arrives, to LAST_ACK when it closes too, and to CLOSED when its FIN is acknowledged. CLOSING, dimmed and joined by dashed lines, is reached only when both sides close at once. Each move lights up while the event that causes it is named below. CLOSED SYN_SENT LISTEN SYN_RCVD (SYN received) ESTABLISHED FIN_WAIT_1 CLOSE_WAIT FIN_WAIT_2 CLOSING both close at once LAST_ACK TIME_WAIT CLOSED client: connect(): send SYN client: SYN-ACK in: send ACK client: close(): send FIN client: ACK in client: FIN in: send ACK client: wait 2 × max segment lifetime server: listen() server: SYN in: send SYN-ACK server: ACK in server: FIN in: send ACK server: close(): send FIN server: ACK in FIN: done sending · LAST_ACK: wait for last ACK CLOSE_WAIT: the other side closed first

TCP's eleven states (CLOSED is drawn twice, where a connection starts and where it ends), as RFC 793 defined them in 1981: the client connects and closes first, the server listens and closes second. The side that closes first waits in TIME_WAIT so that stray packets from this connection die before its addresses and ports are reused.

How it works
  1. TIME_WAIT. It lasts twice the maximum segment lifetime; Linux uses 60 seconds.
  2. CLOSING. Reached only when both sides send FIN at the same moment; most connections never see it.
  3. Seeing it live. ss -tan on Linux lists every connection with its current state.
04 · Flow and congestion control

How fast is too fast? Ask the network.

After the handshake, the sender must decide how fast to send. Too fast: the receiver drops packets, queues in the network overflow, and the connection wastes work and degrades. Too slow: bandwidth sits idle. The right speed changes from millisecond to millisecond as other flows enter and leave the network. No central authority tells TCP the answer; each connection must find it for itself, in real time, from the only feedback it has: acknowledgements arriving (or not arriving). The result is distributed feedback control.

The first piece is the sliding window, built on the receive window that every ACK restates as the receiver's buffer fills and drains. As ACKs arrive, the trailing edge of the window advances and new bytes can be sent at the leading edge, so the window slides forward through the byte stream. That is flow control: the sender never overwhelms the receiver's ability to consume.

Fig 10.4 · The sliding window
Fig 10.4 · The sliding window A sender's view of bytes 100 to 114 with a window of six bytes. At first bytes 100 to 103 are in flight and 104 and 105 may still be sent. Then 104 and 105 go out and the window is full. An ACK numbered 103 arrives, meaning 100 to 102 are received: the window slides to cover 103 to 108, and 106 to 108 may be sent. They go, the window is full again, and ACK 109 slides it on to 109 to 114. BYTES 100 TO 114 · WINDOW 6 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 window 4 in flight, 2 more allowed window full: wait for an ACK ACK 103: the window slides by 3 sent 106 to 108: full again ACK 109: it slides on acked in flight may send not yet cells show the last two digits

Bytes 100 to 114 with a six-byte window: ACK 103 says bytes 100 to 102 arrived, so the window moves up to 103 and three more bytes may go; ACK 109 moves it on by six.

How it works
  1. ACK numbers. "ACK 103" means every byte before 103 arrived; TCP acknowledges the stream, not individual packets.
  2. Two limits. The receiver's window protects the receiver; the congestion window, in the next figure, protects the network. The sender obeys the smaller.
  3. Real sizes. Windows run to megabytes on fast long links; the window-scale option lets the 16-bit field describe them.

The sliding window prevents the sender from outrunning the receiver. It does nothing to prevent the sender from outrunning the network. That is what congestion control is for, and it is the deeper of the two problems.

In October 1986, the throughput between Lawrence Berkeley National Laboratory and UC Berkeley (two sites four hundred yards apart, three network hops between them) collapsed from its usual 32 kilobits per second to roughly 40 bits per second. A factor of eight hundred. The collapse was sudden, no hardware had failed, and no one knew why. The two endpoints assumed they could simply send at their advertised window size; the network's queues filled; packets were dropped; TCP retransmitted; the retransmits piled into the same queues; the queues overflowed again. Each retransmit produced more retransmits, while neither side was, locally, doing anything wrong. Van Jacobson at LBL and Michael Karels in Berkeley's UNIX group, one at each end of the collapsed link, spent the better part of the next two years finding out why. Their diagnosis, published in 1988 as "Congestion Avoidance and Control", was that the receive window accounted for the receiver but said nothing about the network in between, and a network without explicit congestion feedback would always, eventually, congest itself to death. The paper's fix centred on two pieces, explained next: slow start and AIMD, and both became part of every TCP stack in the world over the next four years. Without that fix, the internet could not have grown as it did in the 1990s.

The algorithm has two phases. Slow start begins cautiously. The sender keeps a second limit of its own, the congestion window (cwnd), its guess at what the network can carry, and obeys whichever of it and the receive window is smaller. cwnd starts at one segment and doubles on every successful round trip: exponential growth probes the network's capacity quickly. When packet loss is detected (three duplicate ACKs, the receiver's way of saying one segment vanished while later ones kept arriving), the connection assumes it has hit the network's capacity and switches to congestion avoidance: cwnd is halved, then increases linearly, by one segment per round trip (RTT). Another loss triggers another halving, so the window oscillates around the true capacity in a sawtooth pattern. Adding a constant on the way up and halving on the way down is what AIMD names: additive increase, multiplicative decrease. (A full timeout is grimmer news: the window resets to one segment and slow start begins again.)

Fig 10.5 · The TCP sawtooth
Fig 10.5 · The TCP sawtooth TCP's congestion window, in segments, over 40 round trips, on a path that can carry 24. Slow start doubles it each round trip, 1, 2, 4, 8, 16, 32, until the first loss halves it to 16. From then on it grows by one segment per round trip and halves at each loss, a sawtooth between about 12 and 25 around the capacity, which TCP never learns directly. 0 12 24 capacity 0 20 40 round trips window slow start: ×2 +1 per RTT, halve on loss

On a path that carries 24 segments, slow start overshoots to 32 before the first loss halves the window; from then on it saws between about 12 and 25. TCP never measures the capacity: it finds the limit by overshooting it.

How it works
  1. Reno. The variant drawn here. Tahoe, its 1988 predecessor, fell back to one segment on every loss, not only on a timeout.
  2. The signal. Here loss is the only signal; ECN lets routers mark packets instead of dropping them, and BBR (Fig 10.8) watches delay as well.
Why AIMD: Chiu & Jain, 1989

Chiu and Jain proved in 1989 that AIMD is the only one of the four combinations of additive or multiplicative increase with additive or multiplicative decrease that converges to both efficiency (the network is fully utilised) and fairness (every flow gets an equal share). Multiplicative increase with multiplicative decrease (MIMD) keeps the ratio between the flows and additive with additive (AIAD) keeps the gap, so neither ever gets fairer; multiplicative increase with additive decrease (MIAD) widens the gap.

Fig 10.6 · AIMD's fairness, in phase space
Fig 10.6 · AIMD's fairness, in phase space Flow A's share across, flow B's share up, on a link of capacity 32. A dashed line marks the link full (A plus B equals 32), another marks A equals B. Starting at A 22, B 4, both flows add one per round trip, moving diagonally until the link is full, then both halve, moving toward the origin. The gap between them goes 18, 9, 4.5, 2.25, 1.1: the walk settles onto the fair line, its peaks on the capacity line creeping toward the point where the link is full and the shares are equal. 0 flow A flow B link full A = B: fair full and fair start +1 each both ×½ gap A − B: 18 18 9 9 4.5 4.5 2.25 2.25 1.1 1.1

Adding one segment to each flow per round trip keeps the gap between them; halving both on a loss halves it. Every loss brings the pair closer to equal shares while each peak still fills the link, though neither flow knows the other exists.

How it works
  1. The plane. Flow A's window across, flow B's up. On the blue line the link is exactly full; on the green line the two are equal. The target is where they cross: AIMD ends up oscillating along the green line, its peaks closing in on the target.
  2. The two moves. Additive increase adds the same amount to both, so the point moves at 45° and the gap stays. Multiplicative decrease halves both, so the point slides toward the origin along its own ray and the gap halves.
  3. This run. Capacity 32, start A 22 and B 4, both flows losing at the same moment, as in Chiu and Jain's model. Real flows with different round-trip times converge less neatly.
05 · CUBIC, BBR, QUIC

Three answers to TCP's mid-life crisis.

Classic TCP (Tahoe, Reno, NewReno) was designed for the late-1980s internet, whose links ran at tens of kilobits to a few megabits per second. The modern internet has flows that span continents (200 ms round trips) between gigabit endpoints, and mobile connections whose radio conditions change every second. Classic TCP loses badly on both counts: its slow linear growth takes too long to fill a fast path with a long round trip, and its loss-as-signal model fails on lossy wireless links, where most loss has nothing to do with congestion.

CUBIC (Linux's default since 2006) keeps loss-based detection but replaces the linear additive-increase phase with a cubic function. After a loss the window climbs fast at first, then eases into a plateau as it nears the size that last caused trouble, and only then accelerates past it to probe for new capacity. Of the congestion controls web servers run, it is the most common.

Fig 10.7 · CUBIC vs Reno: same loss, different recovery
Fig 10.7 · CUBIC vs Reno: same loss, different recovery Congestion window over the 30 seconds after one loss, on a 1 gigabit per second path with a 100 millisecond round trip, where the window that last filled the link, W max, is about 8,600 segments. Reno halves to 4,300 and adds one segment per round trip, a nearly flat line that would take about 430 seconds to get back. CUBIC cuts to 0.7 of W max and follows a cubic curve: it rises fast, flattens as it nears W max, reaches it after about 19 seconds, then climbs past it to probe for more. 0 10 20 30 seconds after the loss window 0 the last peak loss ×0.7 ×0.5 CUBIC Reno: +1 per round trip 19 s

After one loss, Reno halves its window and adds a segment per round trip; CUBIC cuts to 0.7 and follows a cubic curve in real time, racing back, slowing near the old peak, then probing past it. With a 1 Gbit/s path and a 100 ms round trip, CUBIC recovers in 19 seconds, Reno in seven minutes.

How it works
  1. The path. 1 Gbit/s × 100 ms = 12.5 MB in flight, about 8,600 segments of 1,460 bytes: the Wmax both flows had reached when the loss came.
  2. Reno. Halves to 4,300 and gains one segment per 100 ms round trip: 4,300 round trips, about 430 seconds, to get back.
  3. CUBIC. W(t) = C(t − K)³ + Wmax with C = 0.4 and a cut to 0.7 (RFC 9438). K = ∛(Wmax × 0.3 / C) ≈ 18.6 s is when it reaches Wmax again; growth is set by the clock, not by round trips. Linux's default since kernel 2.6.19 (2006).

BBR (2016, Google) is more radical: its first version largely set loss aside as a signal. It measures the network directly (bandwidth and round-trip time) and aims for the bandwidth-delay product (BDP), the amount of in-flight data that fills the pipe without overflowing buffers. Google deploys BBR at its edge for YouTube and Search. On long paths that lose packets for reasons other than congestion, BBR can move many times more data than CUBIC; behind home routers with oversized queues its gain is shorter delay, because it keeps the queue nearly empty where CUBIC fills it. It is also more aggressive against competing CUBIC flows, which remains a source of debate.

Fig 10.8 · BBR aims for the bandwidth-delay product
Fig 10.8 · BBR aims for the bandwidth-delay product One bottleneck of 1 gigabit per second with a 100 millisecond round trip, so the pipe holds 12.5 megabytes, and a buffer behind it. As the data in flight grows to fill the pipe, the delivery rate rises to 1 gigabit per second while the round trip stays at 100 milliseconds. Past that point the rate stays flat and the extra data waits in the queue, raising the round trip to 175 milliseconds when the buffer is full and the next packet is lost. BBR aims for the knee, a full pipe and an empty queue; loss-based TCP keeps climbing to the full buffer. pipe queue loss delivery rate 1 Gbit/s round-trip time 100 ms 175 ms BBR aims here loss-based 0 BDP buffer full data in flight

Until the pipe is full, more data in flight means more throughput; after that the extra only waits in the bottleneck's queue, adding delay until the buffer overflows. BBR measures the bottleneck rate and the shortest round trip to sit at the knee, while loss-based TCP climbs until a packet is dropped.

How it works
  1. The pipe. 1 Gbit/s × 100 ms = 12.5 MB: the bandwidth-delay product (BDP), the data in flight that keeps the link busy with nothing queued.
  2. BBR. Bottleneck Bandwidth and Round-trip propagation time: it takes the highest delivery rate and the lowest round trip it has recently seen, paces at that rate, and keeps about one BDP in flight, probing a little above and below to keep both estimates fresh.
  3. Loss-based TCP. Reno and CUBIC grow until the buffer overflows, so with a deep buffer every packet waits in the queue (bufferbloat). The first BBR largely ignored loss; later versions react to it too, partly because the first crowded out CUBIC flows.

QUIC (Google 2012, IETF standard 2021) is the most radical answer of all: abandon TCP entirely. QUIC runs over UDP and builds reliability, encryption, many independent streams per connection, and congestion control into one protocol that runs inside the application, not the kernel. Connection setup folds into the encryption handshake of TLS 1.3 (Chapter 11), and a returning client can send its request in its very first packet. Loss in one HTTP request stream doesn't block other streams (over TCP, one lost packet stalls every request queued behind it: "head-of-line blocking"). QUIC is what HTTP/3 runs on: the newest version of HTTP, the web's request-and-reply protocol, in which a browser's GET asks for a page (Chapter 11).

Fig 10.9 · TCP+TLS+HTTP vs QUIC: the same job, two stacks
Fig 10.9 · TCP+TLS+HTTP vs QUIC: the same job, two stacks Two protocol stacks side by side. On the left HTTP/2 sits on TLS 1.3, which sits on TCP in the kernel, on IP. On the right HTTP/3 sits on QUIC, which carries TLS 1.3 inside it, on UDP, on IP. Below, the round trips before a page arrives: over TCP and TLS 1.3, one for the TCP handshake, one for TLS and one for the request, three in all; over QUIC, one combined handshake and the request, two; a resumed QUIC connection sends the request in its first packet and has the page after one. HTTP/2 stack HTTP/3 stack HTTP/2 TLS 1.3 TCP in the kernel IP HTTP/3 QUIC TLS 1.3 inside UDP IP TCP + TLS 1.3 page after 3 QUIC (TLS 1.3 inside) page after 2 QUIC, resumed (0-RTT) page after 1 TCP handshake TLS 1.3 GET → page handshake GET → page GET → page 0 1 2 3 round trips

Over TCP, one round trip goes to TCP's handshake and one to TLS's before the request can go; QUIC's single handshake does both jobs, so the page arrives after two round trips instead of three.

How it works
  1. The stacks. QUIC ships with the browser or server, not the operating system, so it can change as fast as they do: its iterability.
  2. The round trips. With TLS 1.2 over TCP it takes four. 0-RTT resumption sends the request with the first packet to a server the client has met before, so the page arrives after one.
  3. Beyond the handshake. Per-stream multiplexing: a lost packet stalls only its own stream, not every request behind it as on TCP. Connection migration: a connection ID, not the address pair, names the connection, so a phone moving from Wi-Fi to mobile data keeps it.
06 · SYN floods, cookies, and the rest

The price of state.

TCP's strength is that the kernel remembers every connection. TCP's weakness is the same: an attacker who can make the kernel allocate state without ever completing a connection can, with very little effort, exhaust the server's connection tables and lock out legitimate users. The classical attack, the SYN flood, is more than thirty years old and still works against unprotected servers.

The attack uses one feature of the three-way handshake: the server allocates a TCB the moment it receives a SYN, well before the third ACK arrives. An attacker sends thousands of SYN packets per second, each with a different (often spoofed) source IP. The server replies with SYN-ACK to each spoofed source (which goes nowhere or to an uninvolved third party) and waits for the third ACK that never arrives. Each half-open connection holds a slot in the SYN backlog, a short queue kept apart from established connections, for tens of seconds (the SYN_RCVD timeout); at a high enough SYN rate the backlog fills and legitimate clients cannot connect.

Fig 10.10 · SYN flood: half-open connections fill the table
Fig 10.10 · SYN flood: half-open connections fill the table attacker spoofed sources 10 000 SYN/sec target's connection table (SYN_RCVD slots) half-open from 198.51.100.7 half-open from 203.0.113.3 half-open from 192.0.2.45 half-open from 198.51.100.99 half-open from 203.0.113.41 half-open from 198.51.100.5 half-open from 192.0.2.11 half-open from 203.0.113.78 TABLE FULL legitimate clients refused EACH SLOT HELD ~30S WAITING FOR THIRD ACK · ATTACKER SUSTAINS THE FLOOD INDEFINITELY

A flood of SYN packets, each with a different (often spoofed) source IP, arrives at the target. The target's kernel allocates a TCB for each one and parks it in SYN_RCVD waiting for the third ACK. Because the source addresses are spoofed, the SYN-ACK responses go to addresses that didn't ask for anything; no third ACK ever comes. Each half-open entry sits in the table until the SYN_RCVD timeout fires (typically tens of seconds). At a few thousand SYN packets per second (easy for a single home connection to generate), the table fills. New legitimate connections are refused. The classical attack from the 1990s; still works against unprotected servers; the reason every modern OS ships with SYN cookie support and the reason cloud DDoS protection (Cloudflare, AWS Shield) exists.

The defence, invented by Daniel J. Bernstein in 1996, is SYN cookies. When the connection table is under pressure, the server stops allocating state for incoming SYNs at all. Instead, it hides what it needs in the Initial Sequence Number (ISN) it returns in the SYN-ACK. The number is a keyed hash: the two addresses, the two ports and a time bucket, scrambled together with a secret only the server knows into most of its 32 bits. The same inputs always give the same number, and without the secret nobody can produce it. The few bits left carry the MSS (the largest segment the client said it can take), the one setting the server would otherwise have had to remember. The third ACK repeats the addresses and ports and carries the cookie back (its acknowledgement number is the cookie plus one); the server recomputes the hash from them, and a match proves the ACK answers a SYN-ACK the server really sent. Only then does it build the connection and its TCB. Legitimate clients pass; the flood's spoofed connections never send an ACK at all.

Fig 10.11 · SYN cookies: the state goes into the wire
Fig 10.11 · SYN cookies: the state goes into the wire when the SYN backlog gets full, the server stops allocating state and lets the client carry it ① SYN arrives src=A, port=p, seq=X ② compute cookie cookie = hash(secret, A, p, … + time bucket + MSS hint) ③ SYN-ACK with seq = cookie no TCB allocated if 3rd ACK ever comes back… ④ ACK arrives, ack=cookie+1 recompute hash, compare if matches → reconstruct state ⑤ now allocate the TCB connection becomes ESTABLISHED all state derived from the cookie no per-connection memory was used during the flood; only verified ACKs cause state to be created Daniel J. Bernstein, 1996 · enabled by default in modern Linux when the SYN backlog overflows

Under SYN-flood pressure, the server skips the TCB allocation entirely. It computes a cryptographic cookie: a keyed hash of the connection's identifying tuple plus a time bucket plus a few bits encoding the MSS hint the server would normally have remembered. The cookie is returned as the server's Initial Sequence Number. Now the state is in the wire, not in the kernel. If a real client completes the handshake, its ACK number will be cookie+1; the server recomputes the hash from the ACK's tuple, verifies it matches, and only then allocates the TCB. An attacker cannot forge cookies (the secret is unknown) and cannot benefit from spoofed sources (the matching ACK never comes back). The server keeps zero per-connection state during the flood, yet legitimate connections still work. Daniel J. Bernstein, 1996. One of the most beautiful pieces of security engineering ever shipped.

Two other classical TCP attacks turn on sequence numbers. RST injection: an attacker on the path, who can read the live sequence numbers off the passing traffic, injects a TCP RST (reset) segment numbered inside the receiver's window (a receiver ignores anything outside it), causing the receiver to tear down the connection. The Great Firewall of China uses this for censorship: when the firewall sees a request it dislikes, it sends RST segments to both ends, killing the connection and blaming the network. TCP hijacking: an attacker who can predict the sequence numbers a host will use can forge segments in another machine's name, opening a connection that seems to come from a trusted host or injecting data into one already open. The attack had been described by Robert T. Morris (later of the worm) in a 1985 Bell Labs technical report, and was considered theoretical for a decade, until Kevin Mitnick, on Christmas Day 1994, used it on the live internet for the highest-profile intrusion of the era.

Mitnick's target was Tsutomu Shimomura, a security researcher at the San Diego Supercomputer Center known for his work on cellular phone security. Shimomura's home network ran on trusted rsh, a UNIX command that, on the assumption that the source IP could not be forged, granted login access without a password to authorised hosts. But the source IP could be forged, if you also knew TCP's sequence numbers. And Shimomura's machines' ISN generator was not random at all: minutes of probing on Christmas afternoon showed each new connection's ISN stepping up by a fixed 128,000, so the next one was predictable. Mitnick SYN-flooded a trusted host on Shimomura's network so that it could not answer the SYN-ACK it had never asked for with a RST, which would have torn the forged connection down. Then he opened a TCP connection to one of Shimomura's workstations while spoofing the source IP of the now-incapacitated trusted host. Predicting the ISN, Mitnick blind-sent the third ACK plus an immediate echo + + >> /.rhosts command, adding a wildcard entry to the trust file that would let him log in normally afterward. Shimomura discovered the intrusion two days later, traced Mitnick's activity across multiple sites, and led the FBI to Mitnick's apartment in Raleigh, North Carolina, on 15 February 1995. A book (Shimomura and Markoff's Takedown) and, later, a film based on it followed.

The fix came from Steven Bellovin's RFC 1948, about a year after Mitnick's arrest: start each connection's ISN at a clock plus a keyed hash of its addresses and ports, so one connection's numbers reveal nothing about the next. The stacks took the rest of the decade to adopt it, and RFC 6528 made it the standard in 2012. Sequence-number-based hijacking, which had been one of the canonical TCP attacks for fifteen years, became mostly historical. The deeper lesson is that a packet's source address is only a claim, which the network carries but never checks, so applications must authenticate cryptographically on top. That lesson produced TLS (Chapter 11), certificates, and the modern HTTPS web.

State is the attack surface. TCP gives you reliability, ordering, and flow control. It pays for each in kernel-side state that an attacker can force it to spend. The defences mostly move that state where an attacker cannot reach or forge it: SYN cookies (state in the wire), unpredictable ISNs (state an outsider cannot guess), TLS (state cryptographically authenticated).

The seam to Chapter 11

Chapter 10 has built a reliable byte stream on top of an unreliable packet network. Chapter 11 puts the web on top: HTTP for fetching documents, DNS for finding servers by name, TLS for keeping every byte of the conversation private and authenticated. Then the stack is complete: voltage on copper (Chapter 8) → IP packet (Chapter 9) → reliable TCP byte stream (Chapter 10) → HTTP request and TLS encryption (Chapter 11).

Chapter 11

HTTP · DNS · TLS
The Web

The internet is not the web. The internet is the packet network of Chapters 8 through 10 (voltage on copper, IP datagrams, reliable TCP streams). The web is the layer above it: three conventions Tim Berners-Lee devised at a particle physics lab between his 1989 proposal and 1991 (an address, a request, a page format), plus a naming system and the cryptography that rides beneath HTTPS, that together turn a network of packets into a worldwide library of trustworthy documents.

TopicsHTTP · DNS · TLS · CA chain · HTTP/2 · QUIC
Era covered1989 → present
Position fix42.30°N 59.70°W · 2,480 nm run
Chapter 11 hero · HTTP · DNS · TLS The Web IP · best-effort packets TCP · reliable byte stream TLS · encrypted, authenticated GET / HTTP · ask for the page every byte verified by mathematics
01 · Berners-Lee 1989

A proposal that nobody asked for.

In March 1989, a 33-year-old British physicist working at CERN, the European nuclear research lab outside Geneva, submitted a document to his manager titled Information Management: A Proposal. His name was Tim Berners-Lee. The document described a system for linking documents across computers using any network, generalising ENQUIRE, the linked-notes program he had already built for himself at CERN. His manager, Mike Sendall, wrote in the margin of the cover page: "Vague but exciting." He let Berners-Lee carry on. By Christmas 1990 the world's first web server was running on a black NeXT workstation at CERN.

CERN in 1989 was the world's largest particle physics laboratory: thousands of scientists from dozens of countries collaborating on experiments that took years to plan and produced terabytes of data. A detector built in Italy ran software written in France against data analysed in Sweden. Documents lived everywhere: research papers on file servers, calibration tables in plain files on shared disks, equipment manuals on a secretary's PC, design notes in Berners-Lee's own ENQUIRE. Finding anything required knowing where to look; sharing anything new meant emailing it to people you guessed might want it. The information existed; following it from one document to another required a human acting as the link.

Berners-Lee's proposal was that the link itself should be part of the document. A reference in one document should point at another document on another computer, and a click on that reference should fetch and display the target. This is the idea of hypertext, and it was not new: Vannevar Bush had described something like it in 1945 ("As We May Think," The Atlantic); Ted Nelson had named it in 1965 and spent three decades trying to build a perfect version (Project Xanadu, which never shipped). What Berners-Lee added was the recognition that hypertext's bottleneck was no longer the idea but the plumbing. TCP/IP was widespread by 1989 and UNIX workstations were common; what remained was to define a few simple conventions (how documents are addressed, how they are requested, how they are formatted) and implement them.

He defined three:

  • URL: Uniform Resource Locator. A way to write down where any document lives, in any system. http://info.cern.ch/hypertext/WWW/TheProject.html: protocol, host, path.
  • HTTP: HyperText Transfer Protocol. The procedure for one machine to ask another for a document at a URL and receive it back. Originally a one-line request: GET and the path.
  • HTML: HyperText Markup Language. A simple text format in which a link is written inside the sentence it belongs to, between tags that carry the target's URL: <a href="http://info.cern.ch/">the project</a>.

He wrote both ends himself: the server was a program called httpd; the client was a program he called WorldWideWeb, both browser and editor in one window. The first page he served was a description of the project, at the address info.cern.ch. (The original page was reconstructed in 2013, and the URL still resolves.) On 6 August 1991 he posted a short message to the Usenet newsgroup alt.hypertext announcing the World Wide Web project and inviting other implementations: the public birth of the web.

Fig 11.1 · March 1989 to August 1991 · a quiet two and a half years
Fig 11.1 · March 1989 to August 1991 · a quiet two and a half years A timeline from 1989 to 1994, drawn to scale. March 1989, Berners-Lee's proposal, which his manager marked “vague but exciting”. December 1990, the first server, info.cern.ch, on a NeXT cube. August 1991, the announcement on alt.hypertext. December 1991, a lab in California runs the first server in North America. Then a gap until April 1993, when CERN puts the software in the public domain; the take-off follows, and by late 1993 there are more than 500 web servers. 1989 1990 1991 1992 1993 1994 take-off after the giveaway 2½ quiet years, one lab Mar 1989 · the proposal Sendall: “vague but exciting” Mar 1989 · the proposalSendall: “vague but exciting” Dec 1990 · first server info.cern.ch, on a NeXT cube Dec 1990 · first serverinfo.cern.ch, on a NeXT cube Aug 1991 · the announcement posted to alt.hypertext Aug 1991 · the announcementposted to alt.hypertext Dec 1991 · a lab in California first server in North America Dec 1991 · a lab in Californiafirst server in North America Apr 1993 · public domain CERN gives the software away Apr 1993 · public domainCERN gives the software away late 1993 · 500+ servers late 1993 · 500+ servers

For two and a half years the web was one project at CERN: a proposal, a server running by December 1990, an announcement in August 1991. It took off after CERN gave the software away in April 1993; by the end of that year there were more than 500 servers.

How it works
  1. The scale. Each dot sits at its date. The shaded band is the quiet, from the proposal to the announcement; the cluster at the bottom is the take-off after the giveaway.
  2. The machine. A NeXT cube in Berners-Lee's office ran the server, httpd, and his browser-editor, WorldWideWeb. A label on it read “This machine is a server. DO NOT POWER IT DOWN!!”
  3. The first page. info.cern.ch/hypertext/WWW/TheProject.html described the project itself. Paul Kunz brought the software from CERN to SLAC, the California lab whose server went live on 12 December 1991.

"The Web is more a social creation than a technical one."

Tim Berners-Lee, Weaving the Web (1999)

None of the three conventions required anyone else's permission. HTTP runs on top of TCP, which runs on top of IP, which runs on the existing physical internet. URLs needed no central registry: any owner of any host could just start serving documents under their own domain. HTML was just text; anyone could write it. There was no Web Consortium yet and no fee to join. The web spread because the cost of joining was zero and the value of having joined was that you could now find things that other people had decided to publish. Within five years of that Usenet post the number of web servers was doubling every four months.

The architectural choice that mattered

One decision separates the web from the hypertext systems before it. Berners-Lee allowed dangling links. In Project Xanadu, every document was to live inside one shared system that kept its address permanent and tracked every link in both directions, so no link could ever break; someone had to run that system and decide what went into it. In the web, a link is just a URL; if the page behind the URL is gone, the click gets back a 404, the web's "not found" code, and the publisher of the link is none the wiser.

This sounds like a flaw, and it produces "link rot" as the web ages, but it was the price of decentralisation. A system that demands a working link to every published target is a system with a central authority. A system that tolerates 404s is a system anyone can join without asking.

The 1993 release. On 30 April 1993 CERN put the web's software (the line-mode browser, the basic server and the library of common code) into the public domain, giving up all rights to it so that anyone could use, copy, change and pass it on. Until that day the code was CERN's property: free in practice, but nothing in writing stopped CERN from charging for it. Releasing the technology rather than licensing it foreclosed the possibility that any single company could capture it, which helps explain why the closed online services of the day (AOL, CompuServe, MSN), built to keep their members inside their own networks, all ended up opening onto the web instead of replacing it.

02 · HTTP

Five lines of text. Forever.

HTTP is simple enough to speak by hand. Open a TCP connection on port 80 to a web server, type GET / HTTP/1.0, press Enter twice, and the server will reply with a status line, a few headers, and the document (or, on many sites today, a redirect to the HTTPS address). That transcript is HTTP. Three and a half decades of evolution have added connections that stay open between requests, many requests at once on one connection, compression, and finally a binary format, but the conversational shape "client asks, server answers" has not changed: HTTP/1.0 from 1996 still works against modern servers.

The first line of a request is the request line: a verb (called a method), a path, and a protocol version. Then come headers, one name-colon-value pair per line, ended by a blank line. After the blank line, optionally, comes a body (for methods like POST and PUT that send data). A response has the same shape: a status line with the protocol version, a numeric status code (200, 404, 500, …) and a short reason phrase, then headers, a blank line, and the body.

Fig 11.2 · A complete HTTP exchange · request and response, byte for byte
Fig 11.2 · A complete HTTP exchange · request and response, byte for byte An HTTP/1.1 request, line by line, each line ending in carriage return and line feed: GET /index.html HTTP/1.1, then the headers Host: example.com, Connection: keep-alive and Accept: text/html, then an empty line. The response: HTTP/1.1 200 OK, the headers Content-Type: text/html and Content-Length: 1256, an empty line, then the body, an HTML page of 1256 bytes. A key reads: \r\n = end of line. request · client to server \r\n = end of line GET /index.html HTTP/1.1\r\n Host: example.com\r\n Connection: keep-alive\r\n Accept: text/html\r\n \r\n request line headers blank line one TCP connection, port 80 response · server to client HTTP/1.1 200 OK\r\n Content-Type: text/html\r\n Content-Length: 1256\r\n \r\n <html> …</html> status line headers blank line body1256 bytes

One request and its response, byte for byte; every line ends in a carriage return and a line feed, drawn as \r\n.

How it works
  1. Status codes. The first digit is the class: 1xx informational, 2xx success, 3xx redirection, 4xx the client's error (404: no such document), 5xx the server's (500: it failed while answering).
  2. Where the body ends. The blank line ends the headers; Content-Length says how many bytes of body follow (here 1,256), so the same connection can carry the next request.
  3. HTTP/2 and HTTP/3. The same methods, headers and status codes, but sent as binary frames with compressed headers instead of these lines of text.

The big idea: stateless

HTTP is stateless: the server treats every request as independent. There is no "session" at the protocol level. The server is not required to remember that you asked for /index.html ten seconds ago when you ask for /style.css now, so each request carries everything the server needs to answer it.

Statelessness is inconvenient for any application that needs to remember a logged-in user: every way we fake state on top (the cookie above all, a small value the server hands the browser and the browser sends back with every request) exists to paper over it. But the property is what made the web scale. A stateless server can handle requests from a million different clients in any order, on any thread, on any of a thousand servers behind a load balancer (a front door that spreads requests across them), without any of them needing to share memory. It is why the web survived Facebook, Wikipedia, and YouTube becoming popular without a fundamental redesign of the protocol.

What actually happens when you press Enter

Type https://example.com/ into a browser and press Enter. The browser does not "open the page": it runs four exchanges one after another, and the page appears only at the end.

Fig 11.3 · One HTTPS fetch · four exchanges in a row
Fig 11.3 · One HTTPS fetch · four exchanges in a row A waterfall of the four exchanges behind a browser fetching https://example.com/, with a 10 millisecond round trip to the resolver and 20 milliseconds to the server. DNS turns the name into an address from 0 to 10 milliseconds. The TCP handshake runs from 10 to 30, the TLS 1.3 handshake from 30 to 50, and the HTTP request and response from 50 to 70. Each starts only when the one before it has finished. DNS: name to address 0–10 ms TCP: SYN, SYN-ACK, ACK 10–30 ms TLS 1.3: keys, certificate 30–50 ms HTTP: GET, then 200 OK 50–70 ms 0 10 30 50 70 milliseconds

Fetching one page over HTTPS is four exchanges in a row, each waiting for the one before: DNS finds the address, TCP opens a connection, TLS agrees on keys, and only then does HTTP ask for the page. With a 20 ms round trip to the server, the page arrives at 70 ms; only the last 20 carry the request.

How it works
  1. The setting. A DNS server a 10 ms round trip away that already knows the name, and a web server 20 ms away. A cold lookup that has to walk the DNS tree (Fig 11.6) takes longer.
  2. The overlaps. The opening message of TLS rides with TCP's final ACK, and the GET rides with TLS's last message, so each handshake costs one round trip, not more.
  3. Inside TLS. Once the keys are agreed, the GET and the page cross the wire as encrypted bytes. The browser reads the body as it arrives, then closes the connection or keeps it for the next request.

The methods, and why some of them matter

The method tells the server what kind of operation the request represents. HTTP defines about a dozen, but four dominate: GET (fetch a resource), POST (submit data, possibly creating a new resource), PUT (replace a resource with the supplied data), and DELETE (remove a resource). The choice is not arbitrary; it is part of the contract with the caches, proxies (machines that relay requests on a client's behalf) and other machines along the path. Two properties matter: safety (the request does not change the server's state: GET and HEAD, a GET without the body, are safe) and idempotency (repeating the request has the same effect as a single request: GET, PUT, and DELETE are idempotent; POST typically is not). Caches assume safety (a cache answers a repeated GET from its stored copy without asking the server, which is only allowed for a request that changes nothing); retry logic assumes idempotency.

Fig 11.4 · HTTP methods · what they promise, what caches and clients assume
Fig 11.4 · HTTP methods · what they promise, what caches and clients assume A table of HTTP methods with three columns: safe, idempotent, and whether a cache keeps the reply. GET and HEAD are all three. POST is neither safe nor idempotent, and a cache rarely keeps it. PUT and DELETE are idempotent but not safe, and not kept. PATCH is not safe, whether it is idempotent depends on its format, and a cache rarely keeps it. OPTIONS is safe and idempotent, not kept. Below, a request reaches the server, the reply is lost and the client sends it again. PUT quantity 3 leaves the quantity at 3; POST a new order leaves two orders. A key at the bottom: safe means it changes nothing on the server. method safe idempotent cache keeps GET yes yes yes HEAD yes yes yes POST no no rarely PUT no yes no DELETE no yes no PATCH no depends rarely OPTIONS yes yes no The reply is lost; the client sends again: PUT quantity = 3 client lost qty 1 qty 3 POST a new order client lost orders 0 orders 1 orders 2 safe: changes nothing on the server

A method is a promise to every cache, proxy and retry loop on the path: a safe one changes nothing, an idempotent one has the same effect sent once or twice. When a reply is lost and the client sends again, a PUT leaves the quantity at 3, but a POST places a second order.

How it works
  1. The rest of the table. HEAD is GET without the body; PATCH changes part of a resource; OPTIONS asks what the server allows, which browsers do before some requests to other sites.
  2. Why PATCH depends. It is idempotent only when its contents are: a patch that sets a field to a value gives the same result twice; one that appends to a list does not.
  3. Caches and retries. A shared cache stores GET responses its headers allow and rarely a POST's; a retry library resends GETs and PUTs freely but a POST only with an Idempotency-Key the server checks.

The double-charge problem. Stripe's idempotency-key documentation (and every payment processor's equivalent) exists because of one failure mode: client sends POST /charge, server processes the charge, server crashes before sending the 200, client times out, client retries, and now the customer is charged twice. The fix is for the client to attach a unique Idempotency-Key header; the server records the key alongside the result of the first execution and, on a duplicate request with the same key, returns the stored result instead of re-executing. A non-idempotent operation becomes effectively idempotent. Every serious payment, signup, or stateful POST in production runs through some version of this dance, because nothing in HTTP makes a POST safe to resend, and any reply can be lost on the way back.

03 · DNS

The phone book of the internet.

HTTP needs a destination, and on the wire the destination is an IP address: a 32-bit number on the IPv4 internet, 128 bits on IPv6. Humans do not remember addresses like 192.0.2.44; they remember names like example.com. Something has to translate the second into the first, millions of times per second across the planet, in well under a second, and almost never lie. That something is the Domain Name System, a database spread across thousands of servers, arranged as a tree, and copied and cached everywhere, designed by Paul Mockapetris in 1983 (RFC 882, RFC 883, then refined into RFC 1034 and RFC 1035 in 1987). DNS is older than the web and older than most readers of this book. It is also, structurally, the most fragile critical piece of the modern internet, and the one most often abused.

Before DNS, the ARPANET kept a single text file called HOSTS.TXT at the Stanford Research Institute, listing every machine's name and IP; every machine on the network downloaded it periodically. By 1983 the file listed more than 500 machines and was edited by hand. A typo at SRI could break name resolution for the entire network. Mockapetris's design replaced this with a tree: the responsibility for each piece of the name space is delegated to a different organisation, and each organisation runs the servers that hold the official answers for its own piece. So www.example.com is translated not by one machine but by a chain of them, each pointing the asker one step closer to the answer.

The hierarchy

Names in DNS are read right to left, from the root of the tree to the leaf. A trailing dot, almost always omitted in writing, is the root itself. So www.example.com. means: the root, then the com top-level domain, then example registered inside com, then a host called www inside example.com's zone, the part of the tree its owner answers for. Each level is run by different operators. The root is run jointly by twelve organisations (VeriSign, ICANN, university and government bodies) operating thirteen logically named servers (a.root-servers.net through m.root-servers.net) copied to well over a thousand sites worldwide using anycast: one address, answered by whichever copy is nearest. The com servers are run by VeriSign on contract with ICANN. The example.com servers are run by whoever owns example.com: a hosting company, a corporate IT department, a CDN.

Fig 11.5 · The DNS namespace · a tree, read right to left
Fig 11.5 · The DNS namespace · a tree, read right to left The name www.example.com. above a slice of the DNS tree. At the top the root, run by 12 operators; below it top-level domains such as com and fi, the last run by another body; a label marks the top node as the root; under com, names such as example and google; under example.com, hosts such as www and mail. Reading the name from the right walks the tree from the top: the final dot is the root, then com, run by VeriSign, then example, run by whoever owns it, then www, one host in that zone. www.example.com. read from the right; the last dot is the root . root com fi … example google … www mail root servers:12 operators root servers:12 operators com: VeriSignfi: others com: VeriSignfi: others example.com:whoever owns it example.com:whoever owns it www: one hostin that zone www: one hostin that zone

The bold path is the name read from the right. Beside each level stands who runs it, and www is only one host inside example.com's zone.

How it works
  1. The levels. net is run by VeriSign too, org by Public Interest Registry, each country code by a national registry (fi by Traficom).
  2. The trust. About 360 million registered names, and nobody below the root checks anyone else's answers: each operator is trusted for its own subtree.

How a name becomes an address

When your laptop wants to know example.com's IP, it asks its configured resolver: your ISP's, or a public service like Cloudflare's 1.1.1.1 or Google's 8.8.8.8, usually reached through your home router. If the answer is in the resolver's cache, it comes straight back. If not, the resolver does the actual work, called recursion. It asks one of the root servers for example.com; the root replies, "I don't know, but the com servers do. Here are their IP addresses." The resolver asks one of the com servers; com replies, "I don't know, but the example.com servers do. Here are theirs." The resolver asks one of those; that server is authoritative for example.com and returns the actual IP. The resolver caches the answer for its time to live (TTL), a span of time the authoritative server sets (typically 5 minutes to 24 hours; not the hop count of the IP header's TTL, Fig 9.4), and returns it to the laptop. Total time: usually under 50 milliseconds the first time; until the TTL expires, every later lookup costs one short trip to the resolver, or nothing at all when the laptop's own cache still holds the answer.

Fig 11.6 · Recursive resolution · four servers, four questions, one answer
Fig 11.6 · Recursive resolution · four servers, four questions, one answer A cold DNS lookup of example.com. The laptop asks its resolver once. The resolver asks a root server, which refers it to the com servers; asks a com server, which refers it to example.com's own servers; and asks one of those, which returns the address. The resolver caches the answer and passes it to the laptop. root (.) → ask com → ask com com servers → ask example.com → ask example.com example.com → the address → the address resolver ISP or 1.1.1.1 cache: empty cache: 1 hour laptop example.com? has the address asks once

Only the resolver walks the tree; the laptop asks once and gets one answer. Held in the resolver's cache for an hour, that answer spares the next asker the whole walk.

How it works
  1. On the wire. Queries go over UDP port 53. If an answer is too big for the UDP message size the asker said it can take, the server marks it truncated and the asker repeats the query over TCP. DNS over HTTPS and over TLS carry the same messages encrypted.
  2. The cache. A busy resolver already holds the root and com referrals, so most lookups skip to the last step or need no walk at all.

Where the trust breaks

DNS was designed before adversarial thinking became standard in network protocol design. The resolver trusts an answer signed by nothing: the query goes out as a UDP packet with a 16-bit transaction ID, and whichever server replies first with the matching ID and a plausible answer is believed. A reply must also arrive at the port the query left from, but resolvers then used the same port every time, so only the ID had to be guessed. If an attacker can guess the ID and beat the real reply to the resolver, the resolver caches the attacker's lie and serves it to every user behind the resolver until the TTL expires. This is cache poisoning, and it has been theoretically known since the 1990s. In 2008, security researcher Dan Kaminsky discovered a practical, fast variant that worked against essentially every DNS resolver in deployment. Until then a lost race was costly: the real answer sat in the cache, and the same name could not be raced again until its TTL ran out. Kaminsky asked about made-up names instead (1.bank.com, 2.bank.com and so on), each a fresh race, and had every forged reply also claim that the attacker's server answered for all of bank.com. He disclosed it privately, and Microsoft, Cisco, the BIND maintainers and dozens of others released coordinated patches on a single day in July 2008. The patch did not fix the underlying weakness (UDP replies are still forgeable in principle) but added source port randomisation, multiplying the attacker's guessing space from 2¹⁶ to about 2³², so each forged reply is tens of thousands of times less likely to match.

Fig 11.7 · Cache poisoning · the attacker races the real answer
Fig 11.7 · Cache poisoning · the attacker races the real answer DNS believes whoever replies first with a matching transaction ID resolver cache empty "bank.com? id=0xA73F" real bank.com NS id=0xA73F · 203.0.113.7 arrives at t = 30 ms attacker, source-spoofed id=0xA73F · 198.51.100.66 arrives at t = 12 ms → believes the attacker → caches 198.51.100.66 → ignores real answer (duplicate response, wrong source) DEFENCE · randomise the source port too: 16-bit ID × 16-bit port = 32-bit guess space ~65,000× harder to forge · Kaminsky 2008 patch

DNS cache poisoning in one picture. The resolver sends a query out; the attacker, who has guessed (or been told) a transaction ID, fires forged replies at the resolver from a spoofed source IP. If a forged reply with a matching ID arrives before the real one, the resolver caches the lie and serves it to every user behind it for the TTL duration, typically hours. Kaminsky's 2008 disclosure showed how to make this practical against unpatched resolvers in minutes. The fix, randomising the source port as well as the transaction ID, multiplied the attacker's search space ~65,000-fold and pushed the practical attack from minutes to years. DNSSEC is the deeper fix: cryptographically sign every record so resolvers can verify authenticity, not just guess less. DNSSEC has been deployed for two decades and still covers under half the global namespace, because the deployment cost is real and the perceived risk has fallen.

The 2016 Dyn attack. On 21 October 2016, the Mirai botnet (about 100,000 hijacked home devices such as cameras and routers) directed sustained UDP floods at Dyn, a major DNS provider that hosted authoritative servers for Twitter, Netflix, Reddit, GitHub, Spotify, and dozens of others. For most of the morning on the US East Coast, those sites were unreachable, not because their own servers were down but because nobody could resolve their names. Dyn's engineers fought the flood through three waves across the day before filtering finally held the flood back. The lesson, that even a "decentralised" tree has heavy concentration points, drove the modern push toward redundant DNS: a domain's name servers spread across two or more independent providers, so no single operator going dark takes the name with it.

04 · Cryptography intro

The mathematics that makes the rest of the chapter possible.

Up to here, every protocol we have built (IP, TCP, HTTP, DNS) operates in cleartext. Anyone with access to any wire between you and the server can read every byte you send and receive, and can forge bytes pretending to be either side. That was acceptable when the internet was a few thousand academics. It is not acceptable when the internet is your bank account and your medical records. The fix is cryptography, and the next section, on TLS, needs three of its primitives: hashing, symmetric encryption, and public-key cryptography. This section is a fast briefing, enough to read the TLS handshake; Chapter 14 takes them apart in mathematical depth.

One-way functions: the mathematical asymmetry that makes everything possible

Modern cryptography rests on operations that are easy to do in one direction and computationally infeasible to undo. The canonical example is multiplication. Multiply two large primes, say the roughly 300-digit ones inside a 2048-bit RSA key, and a laptop finishes in microseconds. Hand someone the product alone and ask for the factors, and the best known methods would run for longer than the age of the universe. That asymmetry between "easy forward" and "hard backward" is the lever all of public-key cryptography pulls on.

Fig 11.8 · One-way function · easy forward, infeasible backward
Fig 11.8 · One-way function · easy forward, infeasible backward Forward: 61 times 53 equals 3233, in one step. Backward: to factor 3233 by trial division, divide by 2, 3, 5, 7 and each prime in turn; 53 divides it, on the 16th try, giving 61. Below, the sizes that matter, in digits of the product: 4 for the toy; 250 for a product factored in 2020 at about 2,700 core-years; 617 for a 2048-bit RSA key. easy: multiply 61 × 53 = 3233 1 step hard: factor 3233 ÷ 2no1 try 3no2 tries 5no3 tries 7no4 tries 11no5 tries 13no6 tries 17no7 tries 19no8 tries 23no9 tries 29no10 tries 31no11 tries 37no12 tries 41no13 tries 43no14 tries 47no15 tries 53= 6116 tries at real sizes, digits of the product: 4 the toy: 16 tries 250 factored in 2020: ~2,700 core-years 617 2048-bit RSA key: out of reach

Multiplying two primes is one step; getting them back from the product is a search. For 3233 it takes 16 tries; a 250-digit product took about 2,700 core-years in 2020, and a 2048-bit RSA key has 617 digits.

How it works
  1. The toy. 61 × 53 = 3233 is the textbook RSA example. Trial division tries each prime up to √3233 ≈ 57 and finds 53 on the 16th.
  2. The state of the art. RSA-250 (829 bits) took about 2,700 core-years in February 2020. In September 2026 GPU clusters running the same General Number Field Sieve pushed the public record to RSA-896, 270 digits. Each extra digit makes the job harder; 617 digits is far beyond any known classical method.
  3. Why it matters. The product goes into the public key; the private key is computed from the primes. Shor's algorithm (1994) would factor quickly on a large enough quantum computer, which is why post-quantum cryptography is being deployed.

Hashing: the digital fingerprint

A cryptographic hash function is the simplest one-way operation. Feed in data of any size and the function returns a fixed-length string of bits (256 bits for SHA-256). Three properties matter: determinism (the same input always produces the same hash), preimage resistance (given the hash, you cannot practically find any input that would produce it), and the avalanche effect (a single-bit change in the input produces a completely different output, with no predictable relationship). The hash is, in effect, a fingerprint of the data: no two inputs sharing a SHA-256 hash have ever been found, and the hash reveals nothing of the data. SHA-256 is the workhorse: the certificates of Section 05 rely on it, and so do most TLS handshakes. (Git is the exception: it still fingerprints its commits with the older SHA-1.)

Fig 11.9 · SHA-256 avalanche · one bit changed, about half the output flipped
Fig 11.9 · SHA-256 avalanche · one bit changed, about half the output flipped Two sentences one bit apart, ending lazy dog and lazy doe, and their SHA-256 hashes, each labelled as that sentence's hash in base 16: d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592 and 90e9129e7b05915b0d2331cb80d82046d8575ac621c921935e3c430b78f101a0. A 16 by 16 grid shows the 256 bits of the first hash, then of the second, then only the bits that differ: 124 of 256, scattered with no pattern. "The quick brown fox jumps over the lazy dog" d7a8fbb3 07d78094 69ca9abc b0082e4f 8d5651e4 6d3cdb76 2d02d0bf 37c9e592 dog's hash base 16 "The quick brown fox jumps over the lazy doe" 90e9129e 7b05915b 0d2331cb 80d82046 d8575ac6 21c92193 5e3c430b 78f101a0 doe's hash base 16 bits ofdog's hash bits ofdoe's hash bits thatdiffer:124 of 256

Change one bit of the input (g is 01100111, e is 01100101) and about half of SHA-256's 256 output bits flip, here 124, in no visible pattern. That is what makes a hash a fingerprint: any change at all, however small, gives a completely different one.

How it works
  1. The grid. The 256 bits of each hash, 16 to a row, a filled square for each 1 (each hex digit above is four of them): first dog's hash, then doe's, then only the squares where they differ.
  2. Why it matters. Publish a file's hash and anyone can re-hash the download and compare; one changed bit anywhere gives a different hash. Git's commit IDs (still SHA-1 by default), Bitcoin's proof of work and Merkle trees rest on the same property.
  3. What it is not. A hash is not encryption: there is no key and nothing to decrypt. The input cannot be worked back from the output, only checked against a guess.

Symmetric vs asymmetric: who has the key

Encryption comes in two flavours, and TLS uses both. Symmetric encryption uses a single shared secret key: both parties have it, both can encrypt and decrypt with it, anyone who gets a copy can read everything. The flagship algorithm is AES (Advanced Encryption Standard, NIST 2001), which encrypts 128-bit blocks using a 128, 192, or 256-bit key in ten to fourteen rounds of byte-level mixing. AES is fast: your CPU has instructions built for it, and a modern laptop encrypts gigabytes per second per core. The hard part is getting the shared key into both parties' hands without anyone watching.

Asymmetric (public-key) encryption solves that bootstrap problem. Each party has a key pair: a public key they share with the world, and a private key they keep secret. Data encrypted with the public key can only be decrypted with the matching private key. The pair also works the other way round: a value computed from a message's hash with the private key can be checked by anyone holding the public key, and only the private key's owner could have produced it. That value is a digital signature, and it is how a server proves who it is (Section 05). A related trick lets two strangers who have never met agree on a shared secret over a public channel. In Diffie–Hellman (1976), which is key agreement rather than encryption, they swap public keys; each side combines its own private half with the other's public half, both arrive at the same number, and no eavesdropper can derive it even with full transcripts. Public-key operations are slow (RSA is a thousand times slower than AES for the same data volume, elliptic-curve methods are faster than RSA but still far slower than AES), so in practice we use public-key cryptography only to agree on a symmetric key, then encrypt the actual conversation with AES. That split is what makes TLS practical.

Fig 11.10 · Symmetric vs asymmetric · the same secret · or two halves of one
Fig 11.10 · Symmetric vs asymmetric · the same secret · or two halves of one Symmetric: Alice and Bob both hold the same key K; a message leaves Alice, crosses as unreadable ciphertext and is opened by Bob with the same K. The question is how K reached Bob. Asymmetric: Bob holds a public key (pub) and a private key (prv). The public key crosses openly to Alice; she locks the message with it; only Bob's private key, which never leaves him, opens it. TLS uses both: key pairs to agree on a shared key K, then AES with K for the data. symmetric: one shared key Alice Bob K K hi x7f hi fast, but how did K reach Bob? asymmetric: a key pair Alice Bob pub prv pub pub hi x9q hi public key locks, only private unlocks TLS uses both, once per connection: key pairs key pairs shared K shared K AES data AES data

Top, one key K that must somehow reach Bob; below it, Bob's key pair, whose public half can cross openly. TLS uses the pair once per connection to agree on K, and AES with K carries the data.

How it works
  1. Asymmetric. In asymmetric encryption the two keys are linked by mathematics (Fig 11.8): locking with the public key is easy, opening without the private key means solving the hard problem. Every operation is far slower than AES, so it never touches bulk data.
  2. In TLS. Section 05 shows the exchange inside the handshake (Fig 11.11).
05 · TLS

One round trip · two strangers · a perfect channel.

TLS (Transport Layer Security) is the protocol that turns the cleartext byte stream of TCP into an encrypted, authenticated one. It is what the padlock in a browser's address bar has long stood for, and it runs underneath HTTPS, secure email, and roughly every protocol with a secure variant since the late 1990s. Its lineage runs from SSL 1.0 (Netscape, 1994, found broken before it was ever released) through SSL 2.0 (1995, broken) and SSL 3.0 (1996, eventually broken via POODLE in 2014) to the renamed TLS 1.0 (1999), TLS 1.1, TLS 1.2 (2008, dominant for a decade) and finally TLS 1.3 (RFC 8446, August 2018): the clean version your browser uses today.

The protocol's job is simple to state: two parties who have never met should end up sharing a secret key, with the server's identity proven, while no third party listening to or manipulating the conversation can read or alter the result. TLS 1.3 does it in one round trip (TLS 1.2 needed two) and uses all three tools of Section 04. The client's hello already carries its half of a fresh Diffie–Hellman exchange; the server answers with the other half, and from that moment both sides hold the same secret, so the server can encrypt the rest of its very first reply: the certificate (a signed file tying the server's name to its public key, unpacked below) and a signature, all encrypted. The signature, made with the private key that matches the certificate's public key, proves the server is who the certificate says; the closing messages each carry a hash of every handshake message so far, mixed with the new key; each side computes its own, and if anything was altered on the way the two do not match. Symmetric encryption then carries the data. The whole thing costs one round trip: about 20 milliseconds to a nearby server, three or four times that across a continent.

The TLS 1.3 handshake

Fig 11.11 · TLS 1.3 · one round trip from "hello" to encrypted data
Fig 11.11 · TLS 1.3 · one round trip from "hello" to encrypted data The TLS 1.3 handshake as a ladder, client on the left, server on the right, time running down. The client sends ClientHello with its key share, its half of a Diffie–Hellman exchange; from that moment the server can derive the keys. The server replies with ServerHello and its key share in the clear, then its certificate, signature and Finished, encrypted. One round trip after its hello the client holds the same keys; it sends Finished and its GET request, encrypted, and the server answers with the page, encrypted. client server 1 RTT ClientHello + key sharemy half of a Diffie–Hellman ClientHello + key sharemy half of a Diffie–Hellman ServerHello + key share{certificate, signature, Finished} ServerHello + key share{certificate, signature, Finished} {Finished} {GET /}the request, already encrypted {Finished} {GET /}the request, already encrypted {200 OK and the page} {200 OK and the page}

Gold marks when each side holds the keys: the server as soon as the ClientHello lands, the client when the ServerHello arrives, one round trip after sending its own. Everything after that point is encrypted, the request included.

How it works
  1. Plain and braced. Only the two hellos cross in the clear; everything in braces is encrypted under keys derived from the shared Diffie–Hellman secret. The gold lane marks when each side holds them.
  2. Proof of identity. The X.509 certificate binds the server's public key to its name (Fig 11.12); the signature, over the handshake so far, proves the server holds the matching private key. Each Finished is a hash of the whole transcript made with the new keys, so any tampering shows.
  3. The cipher suite. The hellos also settle the cipher suite, the symmetric cipher (AES, for instance) that will carry the data. A client returning to a server it has met can even send early data with its hello (0-RTT).

The certificate chain

What does it mean for the client to "verify the certificate"? The server's certificate is a small file containing the server's public key, the hostnames it claims to serve, validity dates and a digital signature by some certificate authority the client already trusts. That authority's own certificate is signed by a more trusted one, and so on up the chain. The chain ends at a root certificate: a self-signed certificate that the client trusts because it was shipped with the operating system or browser. There are roughly 150 such roots in the major stores, held by a few dozen organisations such as DigiCert and Let's Encrypt (one operator often controls several), and the whole system rests on the trust placed in those operators.

Fig 11.12 · The certificate chain · why your browser trusts a stranger
Fig 11.12 · The certificate chain · why your browser trusts a stranger Three certificates. At the top the root, ISRG Root X1, which signs itself and ships in the browser's trust store. It signs the intermediate R11, which signs the leaf for shop.example, valid 90 days. The browser checks upward: the leaf's signature with R11's public key, R11's signature with the root's public key, and finally that the root is in its trust store. ISRG Root X1 self-signed, in the store R11 intermediate shop.example leaf, 90 days signs signs ✓ ✓ ✓ ✓ ✓ ✓ checkwith key checkwith key checkwith key checkwith key

A server sends its own certificate and the intermediate's; the browser checks each signature with the public key of the certificate above it, until it reaches a root it already trusts. Here shop.example is vouched for by R11, and R11 by ISRG Root X1, which ships in the trust store.

How it works
  1. What is checked. On the leaf: the name matches the site, today is within its dates, and it has not been revoked. On every link: the signature verifies with the parent's public key. Any failure ends in a browser warning.
  2. Why an intermediate. The root's private key stays offline; the intermediate's signs every day and can be replaced without touching anyone's trust store. Let's Encrypt certificates last 90 days.
  3. Certificate Transparency. Since 2018 Chrome has required every new certificate to be logged publicly, so one issued wrongly, as by the breached Dutch CA DigiNotar in 2011, cannot stay hidden.

Forward secrecy: protecting yesterday's data

The TLS 1.3 key exchange uses ephemeral Diffie–Hellman keys: fresh random values generated at the start of each connection, never reused, discarded the moment the connection ends. This gives forward secrecy: even if an attacker records every encrypted byte of every TLS connection your server has ever served, and then years later steals your server's private key, they still cannot decrypt any of those past connections. The private key authenticated the server during the handshake, but it did not derive the session key; that came from ephemeral material that no longer exists anywhere.

Fig 11.13 · Forward secrecy · stealing the long-term key tomorrow does not unlock yesterday
Fig 11.13 · Forward secrecy · stealing the long-term key tomorrow does not unlock yesterday Two sessions, in 2024 and 2025, are recorded; in 2026 the server's long-term private key is stolen. With RSA key transport, allowed in TLS 1.2, each session key crossed the wire sealed with that long-term key, so the stolen key opens both recorded sessions. With ephemeral Diffie–Hellman, as in TLS 1.3, the long-term key only signed the handshake and each session's secret was erased when it ended, so both recordings stay locked. 2024 2025 2026 server keystolen server keystolen RSA key transport (TLS 1.2) session K1 recorded readable session K2 recorded readable the stolen key unseals every K the stolen key unseals every K ephemeral Diffie–Hellman (TLS 1.3) session K1 recorded locked session K2 recorded locked key only signed; secrets gone key only signed; secrets gone

If the session key crosses the wire sealed with the server's long-term key, whoever records the traffic and steals that key later can open every past session. With ephemeral Diffie–Hellman the long-term key only signs, and each session's secret is erased when it ends, so the stolen key opens nothing recorded.

How it works
  1. The rule. TLS 1.2 allowed both rows; TLS 1.3 removed RSA key transport, so every full handshake has forward secrecy.
  2. What a stolen key still does. It lets the thief pose as the server from then on, until the certificate is revoked or expires.
  3. What it does not cover. Breaking the Diffie–Hellman exchange itself. A large quantum computer could do that from a recording, which is why recorded traffic is a reason to move key exchange to post-quantum methods now.
06 · HTTP/2 & HTTP/3

The two upgrades that broke compatibility on purpose.

HTTP/1.1, from 1997, was good enough for nearly two decades. Then mobile networks happened, JavaScript-heavy sites happened, the average web page grew from 50 KB to 3 MB, and the protocol's one-request-at-a-time model started costing real time. Two new versions followed, HTTP/2 in 2015 and HTTP/3 in 2022, each fixing a bottleneck the previous one couldn't. Neither is a new HTTP: the headers, methods, and status codes you saw in Section 02 are unchanged. What changes underneath is how those messages are packed onto the network and carried. The new packing is unreadable to old software, so browser and server first agree on a version (HTTP/2 inside the TLS handshake; HTTP/3 once the server has advertised it) and fall back to HTTP/1.1 when either side cannot.

HTTP/2: binary, multiplexed, on the same TCP connection

The headline problem with HTTP/1.1 was head-of-line blocking: a slow response at the front of the queue holds up everything behind it. A browser opening a typical page needed to fetch dozens of resources: HTML, then 10 stylesheets, then 30 JavaScript files, then 50 images. HTTP/1.1 sent these one at a time down a single TCP connection (with at most six parallel connections per host as a workaround). HTTP/2 (RFC 7540, 2015, derived from Google's SPDY) replaced the text-based wire format with a binary one and introduced streams: many independent request/response pairs interleaved on the same TCP connection, each chopped into binary frames tagged with a stream ID. The server can deliver frame 4 of stream A, then frame 1 of stream B, then frame 5 of stream A, and the client reassembles each stream from its frames. All ninety-odd requests now share one connection, with no head-of-line blocking at the HTTP layer.

Fig 11.14 · HTTP/1.1 vs HTTP/2 · serial vs multiplexed on the same TCP connection
Fig 11.14 · HTTP/1.1 vs HTTP/2 · serial vs multiplexed on the same TCP connection Six files over one connection. With HTTP/1.1 they go one after another: html, css, then a 200 millisecond wait while the server prepares slow.js, then slow.js, app, and two images, finishing at 560 milliseconds. With HTTP/2 every response is cut into small frames and the frames of all the files are interleaved; the other files use the wait, slow.js slips in when it is ready, and everything finishes at 360 milliseconds. HTTP/1.1: one response at a time HTTP/2: frames from every stream htmlcssslow.js waitappimageimage ↑ slow.js, when ready done 560 ms done 360 ms 0 200 360 560 milliseconds

On one HTTP/1.1 connection each response must finish before the next starts, so while the server prepares slow.js the wire sits idle. HTTP/2 cuts every response into frames tagged with a stream ID and interleaves them, so the other files use that time and the page is done at 360 ms, not 560.

How it works
  1. The setting. Six files on one connection, widths drawn as wire time; slow.js needs 200 ms of server work before its 20 ms of bytes. Round trips are left out.
  2. The limit. HTTP/2 makes no byte faster; it only fills idle time. And all its frames ride one TCP byte stream, so a lost packet still stalls every stream behind it (Fig 11.15).

HTTP/3: still TCP's fault

HTTP/2 cured head-of-line blocking at the HTTP layer, not at the TCP layer, where Chapter 10 met it. TCP delivers a single ordered byte stream; if one packet is dropped, every byte after it is held back until the missing packet is retransmitted, even bytes that belong to other HTTP/2 streams with no dependency on the missing data. On a clean network the cost is invisible. On a flaky mobile connection, with 5% packet loss and intermittent coverage, that stall dominates page load time. The fix had to happen below HTTP, at the transport layer itself.

QUIC (RFC 9000, 2021; the name began as Quick UDP Internet Connections, but the standard treats it as just a name), the transport Chapter 10 introduced in Section 05, replaces TCP entirely for HTTP traffic. It runs over UDP with TLS 1.3 built in, and each stream is ordered independently, so a lost packet on stream A no longer holds back stream B. QUIC was developed by Google starting around 2012, rolled out experimentally in Chrome and on Google's servers from 2013, and reached essentially all Chrome users by 2017, years before the IETF standard. HTTP/3 (RFC 9114, 2022) is just HTTP semantics over QUIC. By 2024 about one web request in five that Cloudflare served used it (20.5 percent).

Fig 11.15 · HTTP/3 over QUIC · escaping TCP at the transport layer
Fig 11.15 · HTTP/3 over QUIC · escaping TCP at the transport layer Nine packets from three streams, A, B and C, arrive in turn; the fourth, part of stream A, is lost and resent as the tenth. Over TCP every packet after the loss, from all three streams, is held until the resend arrives. Over QUIC the packets of B and C are delivered as they arrive, and only stream A waits. HTTP/2 over TCP: B and C wait for A A B C HTTP/3 over QUIC: only A waits A B C A2 lost A2 resent packets in arrival order

TCP hands the application one ordered byte stream, so when a packet of stream A is lost, the packets of B and C that arrive after it wait too. QUIC orders each stream on its own: B and C are delivered as they arrive, and only A waits for the resent packet.

How it works
  1. The packets. Three streams take turns; the fourth packet, part of A, is lost and resent at the end. A filled square is handed to the application on arrival; a hollow one waits along its dashed line.
  2. Why TCP cannot help. It numbers bytes, not streams (Fig 10.4), so it cannot know that B's bytes do not depend on A's.
  3. The price of the fix. QUIC runs over UDP and ships in the application, so every server, CDN and firewall had to learn it; the handshake folds in TLS 1.3 (Fig 10.9).

Protocols earn their shape. Every protocol in this chapter follows the same arc. Berners-Lee's HTTP was minimal, text-based, perfect for 1991. It was pushed past its limits and succeeded by denser, harder, more cryptographic versions. DNS, designed in 1983 with no adversarial model, was patched in turn with port randomisation, DNSSEC and encrypted DNS. TLS was born as Netscape's SSL 1.0, which was never released because it was broken, and went through six numbered versions before reaching the clean 1.3 most browsers now use. None of these protocols was right at first issue. HTTP was reshaped by load and latency, DNS and TLS by attacks and patches, over twenty-plus years. And the change is slow, because a network protocol cannot iterate quickly: every change has to be deployed across millions of independently operated machines without breaking the existing ones.

The seam to Chapter 12

Chapter 11 has built the web's transport. We can now send a typed URL through a verified, encrypted tunnel; we can negotiate a fresh shared key with a stranger every time; we can name and find any machine on Earth by composing a few labels. What we have not yet done is run code inside the document we just fetched. That takes a programming language baked into every browser: JavaScript. It was invented in ten days, and it never should have worked. Chapter 12 is its story.

Chapter 12

JavaScript
The Language That
Shouldn't Have Worked

In May 1995 Brendan Eich had ten days to build a working prototype of a scripting language for the Netscape browser. The result was committed to a release branch before it had a stable name and shipped before it had a specification. It is arguably the most widely deployed programming language in history. It runs in every browser. It runs on servers via Node.js. Its object-literal syntax became JSON, the format most web services use to exchange data. That this happened is a triumph of practical engineering over good taste, and that it works is down to a series of architectural decisions made under deadline that turned out to be unexpectedly durable.

TopicsEvent loop · Promises · V8 · Node · DOM · same-origin · XSS · CSP
Era covered1995 → present
Position fix40.71°N 74.01°W · 3,129 nm · landfall
Chapter 12 hero · JavaScript The Language That Shouldn't Have Worked let x = 1; x.toFixed() → "1" x holds the number 1;toFixed turns it into texteven a number has methods:things it can do WRITTEN IN 10 days Mocha → LiveScript → JavaScript May, September, December 1995 three names · one language · all of the web
01 · The browser problem

HTML can describe; it cannot do.

By 1994 the web that Berners-Lee had launched in Chapter 11 was growing exponentially, but as a system of static documents: HTML files of text, images, links, perhaps a form that POSTed somewhere. Click a link and the browser fetched a new page; submit a form and the server replied with another page. Every interaction was a full round trip and a full re-render. For a research-paper repository this was fine. For anything closer to an application (validating a form before submission, a stock quote that updates, a clock), it was hopeless. The browser needed to be able to run code.

The first attempt was Sun Microsystems' Java applet. In May 1995 Sun unveiled HotJava, a browser written in Java that could embed small Java programs (applets) inside web pages. Each applet ran inside a Java Virtual Machine, isolated from the host, with a defined API for drawing and user input. On paper it was the right answer: a real language that checked its types before running, walled off from the rest of the computer, with a proper runtime. In practice the JVM took five to ten seconds to start, applets were heavy for small jobs (even a few lines of page logic meant a whole compiled Java program), and the server had to host compiled .class files. Each applet was an island: Java could not easily reach into the surrounding HTML page, nor the page into Java. Applets shipped, carried games and animation, and slowly died: a niche by 2000, obsolete by 2010, dropped by the major browsers between 2015 and 2018.

Netscape, whose browser had overtaken Mosaic as the market leader by early 1995, wanted something different. Marc Andreessen, Netscape's twenty-three-year-old co-founder, believed the web's killer feature would be quick scripts that lived inside the page itself: small snippets with no type declarations that any HTML author could add without compilers and without ten seconds of warm-up. The language should look enough like Java that the buzzword "Java" could be used in marketing, but be far simpler. It should run instantly, fail without taking the browser down with it, and be easy enough for a designer who had learned HTML over a weekend to pick up. Netscape needed it shipped in the Netscape 2.0 release, whose first beta was due that September.

Fig 12.1 · The web of 1994–95 · static pages and a walled-off Java box
Fig 12.1 · The web of 1994–95 · static pages and a walled-off Java box Two browser windows from 1995. In the first, an HTML form: abc is typed into an age field and Send is pressed. The request goes to the server, the whole page goes blank while a new one loads, and the new page says age must be a number. In the second, a Java applet sits in a box on the page: it shows starting Java while a bar fills, and only then runs, inside its box, unable to reach or change the page text around it. HTML form age:abcSend loading a new page… age:abcSendage must be a number server Java applet starting Java… the applet runsin its own box page text the applet can't reach or change

In 1995 a page could only send a form to the server and wait for a whole new page, or hand a box to a Java applet that took seconds to start and could not touch the page around it. What was missing was a small language that ran inside the page itself, at once.

How it works
  1. The form. Every check, even "is this a number?", meant a request to the server and a whole new page back.
  2. The applet. Java ran in its own virtual machine, sandboxed from the computer and walled off from the page; the machine had to start before the first pixel.
  3. What came next. JavaScript (Fig 12.2) shipped inside the page in Netscape 2.0. Applets faded, and the major browsers dropped the plugin interface they needed between 2015 and 2018.
02 · Eich 1995

Ten days. Three names. One language.

Brendan Eich joined Netscape in April 1995. He was 33, and had been recruited with the promise that he would bring Scheme (a Lisp dialect) to the Netscape browser. What he got instead was a different brief. Marc Andreessen and Bill Joy (Sun co-founder, one of the project's sponsors) wanted a small companion to Java that looked like Java, so that it seemed familiar to working programmers, and was easy enough for anyone writing HTML. What went underneath was left to Eich, and he chose the dynamic, interpreted flexibility of Scheme. He had ten days to produce a prototype.

He delivered. The first version, internally called Mocha, was a working interpreter by mid-May 1995. Under the C-like syntax sat Scheme's ideas, some of them finished only in the next two releases: a function was a value that could be stored and passed around like a number, and once functions could nest (1997) it kept access to the variables around it even after the code that made them had finished (a closure). From 1996 objects inherited straight from other objects (an object that lacks a property looks it up on the object it is linked to), an idea taken from David Ungar and Randall Smith's language Self. There were no classes (they came twenty years later, as a shorthand for making that link) and no integers (only floating-point numbers, IEEE 754 doubles all the way down), but it did have implicit type coercion (silently turning one type into another) that anyone who has used JavaScript has cursed.

In September 1995, shortly before launch, Netscape renamed Mocha LiveScript. In December 1995 Netscape and Sun, by then partners over Java, announced the language jointly and renamed it again, to JavaScript. The name change was pure marketing: Netscape wanted to ride Sun's Java publicity. The two languages were and remain unrelated, sharing little beyond the C-derived syntax that hundreds of languages share. But the name stuck, and three decades later it is the dominant programming language by raw deployment count and the source of endless confusion for first-time learners.

Fig 12.2 · May 1995 to ECMAScript 2026 · the slow legitimisation
Fig 12.2 · May 1995 to ECMAScript 2026 · the slow legitimisation A timeline from 1995 to 2026. In 1995 the language is written in ten days and named Mocha, then LiveScript, then JavaScript. 1996, Microsoft ships its own copy in Internet Explorer 3. 1997, a first standard names it ECMAScript, ES for short. 1999, edition 3 (ES3) adds text patterns and error handling. Then a decade without an edition: ES4 is abandoned in 2008, and ES5 arrives in 2009 with a stricter mode and JSON. ES2015 brings classes, modules and promises, and from 2016 to 2026 a new edition ships every June. 1995 2005 2015 2025 1995 · written in 10 daysMocha, LiveScript, JavaScript1995 · written in 10 daysMocha, LiveScript, JavaScript 1996 · Microsoft's copyin Internet Explorer 31996 · Microsoft's copyin Internet Explorer 3 1997 · first standardnamed ECMAScript (ES)1997 · first standardnamed ECMAScript (ES) 1999 · ES3 (edition 3)text patterns, error handling1999 · ES3 (edition 3)text patterns, error handling 2008 · ES4 abandonedtoo big to agree on2008 · ES4 abandonedtoo big to agree on 2009 · ES5 (edition 5)a stricter mode, JSON2009 · ES5 (edition 5)a stricter mode, JSON 2015 · ES2015classes, modules, Promises2015 · ES2015classes, modules, Promises 2016 to 2026 · one a yeara new edition each June2016 to 2026 · one a yeara new edition each June

JavaScript was written in ten days in 1995 and renamed twice for marketing; a standard followed in 1997, then a decade's stall over the ambitious ES4 until it was abandoned in 2008. ES2015 finally brought classes and modules, and since then the language has shipped one edition a year.

How it works
  1. The axis. Dots sit at their dates on a scale of 31 years; the labels are spaced evenly so they can be read.
  2. The names. Sun held the Java trademark, which is why the 1997 standard had to be called ECMAScript.
  3. The stall and the cadence. ES4 grew to include classes, static types and packages; Microsoft and Yahoo objected, and it was dropped in 2008 for the smaller ES5. Since 2015 the TC39 committee ships whatever proposals are ready each June.

"I had to be done in ten days or something worse than JS would have happened."

Brendan Eich, on the early Netscape design

The ten-day legacy. Several JavaScript quirks trace directly to the original ten-day window. typeof null === "object" is a bug that became compatibility-locked: an artefact of how Mocha tagged its values (null shared the object tag). 0.1 + 0.2 !== 0.3 is just IEEE 754 (Chapter 2, Section 06), but with every number a 64-bit float, early JavaScript had no exact integer type to fall back on. Implicit coercion is a pragmatic shortcut that did not get a sober second look: adding an empty array to an empty object, [] + {}, turns both into text ("" and "[object Object]") and joins them, so the sum is "[object Object]". None of these is unfixable in principle; all of them are unfixable in practice, because billions of pages on the live web rely on the exact existing behaviour. JavaScript can grow by careful additions; it cannot remove anything.

03 · The event loop

One thread. Never blocking. Forever in motion.

JavaScript runs on a single thread: one call stack, no fork(), no pthread_create(), no Java-style worker threads sharing variables. That would be a fatal weakness if its slow operations blocked. They are built not to. Every operation that might take time (fetching a URL, reading a file, waiting for a timer) returns immediately, because the browser and the operating system do the waiting outside the JavaScript thread; you hand it a function, a callback, to run when the work is done, and they queue it once it is. The mechanism that hands the free thread its next ready callback is the event loop, the design Node.js later carried from the browser to the server.

The model has four moving parts. The call stack holds the function frames currently executing: the same stack Chapter 3 dissected, in JavaScript form. The macrotask queue (also called the callback queue) holds callbacks ready to run from completed I/O, expired timers, and clicks and other page events. The microtask queue holds callbacks that run as soon as the current task ends, before the next task or the render step: chiefly those of Promises (below; for now, an object standing for a value that has not arrived yet, whose .then(f) queues f as a microtask once the value has arrived, or "settled") and of queueMicrotask(). The render step is when the browser recomputes layout and paints any visual changes. The event loop coordinates them: pull one task off the macrotask queue, run it to completion, drain the microtask queue completely, optionally render, repeat. Forever.

Fig 12.3 · The event loop · stack, microtasks, macrotasks, render
Fig 12.3 · The event loop · stack, microtasks, macrotasks, render Four lines of JavaScript: log 1; a zero-millisecond timer whose callback logs 4; an already settled promise whose callback logs 3; log 2. The script runs to its end on the call stack and prints 1 and 2. The timer callback waits in the macrotask queue, whose callbacks run one at a time, and the promise callback in the microtask queue, which is emptied first. When the stack is empty every microtask runs, printing 3; the browser may render; then the next task runs, printing 4. The console reads 1 2 3 4. console.log(1);// prints 1 setTimeout(log4, 0); // timer: log4 prints 4 promise.then(log3); // done: log3 prints 3 console.log(2);// prints 2 call stack microtasks promises all first macrotasks timers, clicks one at a time script log4 log4 log4 log4 log3 log3 log3 log3 render, if a frame is due console output: 1 2 3 4

The script runs to its end first, printing 1 and 2; its timer callback waits in the macrotask queue and its promise callback in the microtask queue. When the stack empties, every microtask runs (3), the browser may render, and only then does the next task run (4).

How it works
  1. The render step. The browser renders only when a frame is due, about every 16 ms on a 60 Hz screen.
  2. Why 0 ms still waits. setTimeout(…, 0) only queues a task; it cannot run until the current script and every microtask have finished.
  3. The catch. Anything long on the stack, a heavy JSON.parse or a tight loop, stops the loop: no clicks, no rendering. A frozen page and Node.js event-loop lag are the same bug.

Three eras of asynchrony

The event loop's shape has been the same since 1995; microtasks arrived in the 2010s, and Promises are now their main user. What has changed, across three eras, is the way JavaScript code spells "do this when the I/O is done." The first era was callbacks: pass a function to whoever does the work, and it will call your function back when finished. Simple, and the source of the famous callback pyramid of doom: three or four callbacks nested inside each other, indentation marching across the screen, error handling tedious. The second era was Promises (standardised in ES2015, Fig 12.2): an object representing a future value, with .then() and .catch() chained one after another at the same depth. The third era is async/await (ES2017): a shorthand that lets you write asynchronous code as if it were synchronous (const x = await fetch(url);) while the engine runs it on Promises underneath. All three eras coexist in any modern codebase, because legacy never dies.

Fig 12.4 · The same fetch · three eras of async syntax
Fig 12.4 · The same fetch · three eras of async syntax The same job written three ways: get a user, then their orders, then the invoice, then render it. With callbacks each step nests inside the previous one, the code stepping further right. With promises the steps form a flat chain of then calls with one catch at the end. With async and await the steps read like ordinary straight-line code inside a try block. The matching lines light together in all three. callbacks, from 1995 getUser(id, function (user) {   getOrders(user, function (orders) {     getInvoice(orders, function (invoice) {       render(invoice);     });   }); // closes each level }); promises, ES2015 getUser(id)   .then(getOrders)   .then(getInvoice)   .then(render)   .catch(showError); // if any step fails async/await, ES2017 async function show(id) {   try {     const user = await getUser(id); // waits     const orders = await getOrders(user);     const invoice = await getInvoice(orders);     render(invoice);   } catch (error) { showError(error); } }

The same four steps (the user, then the orders, then the invoice, then render) spelled three ways: callbacks nested inside callbacks, a flat chain of promises, and async/await that reads like straight-line code.

How it works
  1. Callbacks. Each step nests inside the last, and every level needs its own error handling; forget one and the error is lost.
  2. Promises. Each .then returns a new promise, so the chain stays flat and one .catch at the end catches a failure anywhere in it.
  3. async/await. Each await pauses the function and lets the loop run other work; when the promise settles, the rest of the function continues as a microtask (Fig 12.3).
04 · V8 and Node.js

An interpreter that pretended to be a compiler · then a runtime that escaped the browser.

JavaScript was originally compiled to simple bytecode and interpreted, slowly, by Netscape's engine: Eich's Mocha, soon rewritten as the engine still called SpiderMonkey today. That was fast enough for the small form checks and page changes of 1996, hopelessly slow for an actual application. By 2008, when Google released V8 alongside the first Chrome browser, JavaScript engines had begun turning into compilers. V8 began by compiling everything straight to machine code and added its first optimising compiler in 2010; today's engines, V8 included, run a tiered pipeline from source code to native machine code: start interpreting at once so nothing waits, watch which functions run most often, and spend the expensive compilation only where the running program proves it will pay. A feedback loop re-optimises as the evidence changes.

Fig 12.5 · V8 · from source to optimised native code, with a feedback loop
Fig 12.5 · V8 · from source to optimised native code, with a feedback loop V8's tiers, top to bottom: the parser turns source into bytecode; the interpreter runs the bytecode at once and notes the types it sees; then three compilers: the first turns it into plain machine code, the second optimises for the types seen, the third optimises hardest. A function called add moves down a tier at a time as it gets hotter, having seen only numbers. Then it is called with a string, the optimised code's assumption fails, and it drops back to the interpreter. parser source to bytecode interpreter runs it, notes types compiler 1 plain machine code compiler 2 optimises for types compiler 3 optimises hardest hotter dropsbackdropsback add has seen: only numbers a string too add

V8 runs every function first in its interpreter, which records the types it sees; as the function gets hotter it moves on to compilers that make ever faster code, betting on those types. If a string arrives where only numbers came before, the bet fails: the optimised code is thrown away and the function goes back to the interpreter.

How it works
  1. The tiers. Each costs more to compile and runs faster: Ignition interprets, Sparkplug (since 2021) translates bytecode straight to machine code, Maglev (since 2023) and TurboFan optimise. The optimising compilers, Maglev and TurboFan, work on background threads while the program runs.
  2. The bet. Optimised code is built for the types seen so far, guarded by cheap checks. When a check fails, V8 rebuilds the interpreter's state and carries on there: deoptimisation.
  3. Elsewhere. SpiderMonkey and JavaScriptCore also run tiers of interpreters and compilers today.

Node.js: V8 outside the browser

In 2009 a developer named Ryan Dahl watched a browser's file-upload progress bar, which works by asking the server again and again how much has arrived, and realised that the standard server-side approach was exactly the wrong shape for systems with many slow connections: servers such as Apache gave each connection its own process or thread, and that thread then sat idle while it waited. Browsers had already solved this with the single-threaded, never-blocking event loop. Why not move that model to the server? He took V8, stripped away the browser, added a C library layer (libev and libeio then, libuv today) that runs the event loop and keeps file and network work off the JavaScript thread, and bound them together with a small JavaScript standard library. He called it Node.js and showed it at JSConf EU in Berlin that November.

Within five years Node had become a mainstream backend for startups; within ten it was one of the most widely used server-side platforms. The reasons are practical, not aesthetic: a single-threaded async-I/O model handles thousands of slow connections (the open connections of a chat app, say) on a single CPU core, because a waiting connection costs Node almost nothing, while a thread-per-request server pays a stack of memory and a scheduler slot for every waiting connection and would have collapsed. The operating system reports which sockets are ready, so waiting on the network takes no thread at all; only file work borrows one of a few shared pool threads (Fig 12.6). And sharing one language between browser and server made web applications simpler to build. Node is not the best server for everything (work that keeps the processor busy is still better served by Go, Rust, or threaded Java), but for "many connections, mostly waiting on I/O" it is hard to beat.

Fig 12.6 · Node.js · V8 + libuv + a JavaScript standard library
Fig 12.6 · Node.js · V8 + libuv + a JavaScript standard library Node's layers: your JavaScript on top, Node's standard library (files, http, net, crypto) below it, then V8, which runs the JavaScript on one thread, beside libuv, a C library that holds the event loop and a pool of four threads, and the operating system at the bottom. A file read travels down into libuv, takes a pool thread and goes to the disk. Meanwhile V8's thread is free and serves other requests. When the read completes, libuv's loop queues the callback and V8 runs it. your JavaScript app.js asks to read a file Node's standard library files, http, net, crypto… V8 one thread runs JS libuv · C library event loop operating system sockets, disks threads runs app.js free: servesother requests runs callback

Node runs your JavaScript on one thread in V8 and hands everything slow to libuv. A file read takes a pool thread and goes to the disk while the JavaScript thread serves other requests; when the read is done, libuv's event loop queues the callback and V8 runs it.

How it works
  1. The layers. Your code calls Node's standard library, written partly in JavaScript and partly in C++ on top of V8 and libuv.
  2. Two kinds of waiting. Sockets need no threads: libuv asks the OS to say when they are ready or done (epoll on Linux, kqueue on macOS and BSD, IOCP on Windows). Files, DNS lookups and some crypto run on the pool, four threads by default.
  3. History. libuv was written for Node in 2011, to bring it to Windows.
05 · The DOM and rendering

From bytes to pixels in five steps.

To show a web page, the browser runs the fetched bytes through a five-stage pipeline called the critical rendering path. The HTML, which marks each piece of the page (a heading, a paragraph, an image) as an element nested inside other elements, is parsed into a tree with one node per element (the DOM). The CSS, the page's style rules for colour, size, font and position, is parsed into another tree (the CSSOM). Together those two parses are the parse stage; the style stage then works out which rules apply to each element and combines the trees into a render tree describing what actually gets drawn. Layout computes where each box goes. Paint draws the pixels, and the browser may paint parts of the page into separate images called layers. Composite stacks the layers into the frame you see, without redrawing them. JavaScript can intervene at any stage: modifying the DOM or the CSSOM, forcing a re-layout, triggering a repaint. Which stages a change re-runs decides whether the page holds 60 frames per second (Fig 12.7).

Fig 12.7 · Critical rendering path · five stages, one ~16 ms frame
Fig 12.7 · Critical rendering path · five stages, one ~16 ms frame The five stages of drawing a page: parse, style, layout, paint, composite. A first load runs all five. Changing an element's width re-runs style, layout, paint and composite. Changing its colour re-runs style, paint and composite but skips layout. Animating a transform on its own layer re-runs only composite. parse style layout paint composite first load new width new colour animate transform

A change re-runs only the stages it touches: a new width means style, layout, paint and composite again; a new colour skips layout; an animated transform (moving, turning or scaling a layer that is already painted) needs only composite. That is why smooth animations move transform and opacity (a fade), not width or height.

How it works
  1. The stages. Parse turns HTML and CSS into the DOM and CSSOM; style matches rules to elements; layout works out every box's size and position; paint draws each layer's pixels; composite stacks the layers into the frame.
  2. The budget. At 60 frames a second each frame has about 16 ms for style, layout, paint and composite, your JavaScript included (Fig 12.3); parse runs when HTML or CSS arrives.
  3. The fast path. A CSS animation of transform or opacity on its own layer runs on the compositor thread, so it stays smooth even while the main thread is busy.

The DOM is a tree of objects exposed to JavaScript through methods like document.querySelector() and element.appendChild(). Changing a DOM node makes the browser redo the pipeline from the stage the change touches onward. The browser batches that work: layout and paint normally run once per frame, however many mutations landed. But code that reads a layout measurement between writes (an element's height here, its position on screen there) forces the browser to stop and re-lay-out synchronously, mid-frame, once for every read that follows a write. That interleaving is how naive loop-and-mutate code gets orders of magnitude slower than doing all the writes together. React, Vue, Svelte, and the rest of the modern frontend framework family exist to keep the page in step with your data, and batching DOM changes is a large part of how. The programmer writes declarative rules ("the page should look like X"); the framework works out only the DOM changes the new data needs and applies them together, so reads rarely land between the writes.

06 · Web security

Four boundaries that keep the web from being one big shared computer.

The browser executes JavaScript from arbitrary websites with no pre-arranged trust. You visit news.example.com and it runs hundreds of kilobytes of code on your machine, while another tab might be logged into your bank and the same machine might have access to your filesystem, your camera, your microphone. The fact that visiting a webpage does not automatically compromise everything else you care about is not magic; it is a few specific, layered defences invented over thirty years in response to attacks that worked.

The same-origin policy: the foundational wall

The oldest and most important browser security boundary is the same-origin policy, introduced in Netscape 2.0, the same release that shipped JavaScript itself. The rule: code loaded from one origin can read and modify only resources from that same origin. An origin is the triple (scheme, host, port), the scheme being the protocol at the front of the URL, https or http, and two URLs share an origin if and only if all three match exactly. So https://a.example.com and https://b.example.com are different origins; so are http://example.com and https://example.com; so are http://example.com:80 and http://example.com:8080. Without this rule, JavaScript on any tab could read the cookies and DOM of any other tab, including your bank's.

Fig 12.8 · Same-origin policy · scheme + host + port must all match
Fig 12.8 · Same-origin policy · scheme + host + port must all match The origin https://www.example.com splits into scheme https, host www.example.com and port 443, the default for https. https://www.example.com/page matches in all three: the same origin. http://www.example.com differs in scheme and in port, 80 by default. https://shop.example.com differs in host. https://www.example.com:8443 differs in port. Each of the last three is another origin. scheme host port https www.example.com 443 origin https://www.example.com/page https www.example.com 443 ? same http://www.example.com/ http www.example.com 80 ? other https://shop.example.com/ https shop.example.com 443 ? other https://www.example.com:8443/ https www.example.com 8443 ? other

The origin https://www.example.com split into scheme, host and port, and four other URLs each compared with it: the path never counts, so only the first is the same origin.

How it works
  1. Default ports. A URL without a port uses its scheme's default, 443 for https and 80 for http, so http://www.example.com differs in two places.
  2. The opt-ins. A server can let named origins read its responses with CORS headers (Access-Control-Allow-Origin); two pages can agree to talk with postMessage.
  3. Cookies. Cookies follow older, looser rules than the same-origin policy: they ignore the port, for one.

Cross-site scripting (XSS): when the page itself is the attack

The same-origin policy keeps origin A's code out of origin B. But what if the attacker can inject their own code into origin B's page? Then their code is, by definition, same-origin with B, and can do everything B's legitimate code can do. This is cross-site scripting (XSS), one of the most common web vulnerabilities for two decades running. It comes in three flavours. Reflected XSS: the attacker crafts a URL with malicious script in the part after the ?; the server echoes that part back into the page; the script executes on whoever visits the URL. Stored XSS: the attacker submits malicious script as a comment or profile field; the server stores it in the database; every subsequent visitor's browser executes it. DOM-based XSS: a similar attack carried out entirely in the browser, by page JavaScript that copies part of the URL (after the ? or the #; the part after # is never even sent to the server) into the page unsafely. The first defence is output encoding: before user text goes into a page, characters such as < and > are turned into codes the browser shows as text instead of reading as tags.

Fig 12.9 · XSS · three ways for attacker code to run with your origin's privileges
Fig 12.9 · XSS · three ways for attacker code to run with your origin's privileges three pipelines, three injection points, one outcome: attacker code in your origin REFLECTED: query string echoed into HTML ① attacker → victim: https://search.example.com/?q=<script>steal()</script> ② server response: <p>Results for: <script>steal()</script></p> ③ browser executes the injected script as same-origin with example.com defence: HTML-escape any user input echoed into HTML; never trust query strings STORED: payload persisted in database, served to every visitor ① attacker posts comment: Nice post! <script>steal()</script> ② server stores it · every later visitor's HTML contains the script ③ all subsequent visitors' browsers execute the script on every page view defence: same as reflected, plus output-encode at render time, not at storage time DOM-BASED: injection happens entirely in the browser, never touches the server ① page JS reads location.hash: document.body.innerHTML = location.hash; ② attacker's URL: https://example.com/#<img src=x onerror=steal()> ③ browser parses the hash as HTML, runs the onerror; server saw nothing suspicious defence: never assign untrusted strings to innerHTML; use textContent or DOM APIs that don't parse HTML

All three XSS variants share the same shape (attacker-controlled string ends up where the browser parses it as code), and all three are exploitable as long as user input flows into HTML, JavaScript, or attribute contexts without proper output encoding. The 2010s saw an industry-wide push toward template engines that escape by default (React's JSX, Vue's mustaches, Svelte's curly braces all encode by default), and toward tainted-string static analysis at build time. Even so, XSS still appears every year on the OWASP Top 10. The deeper fix, the one this section is heading toward, is Content Security Policy, which lets the server tell the browser "do not run inline scripts at all, and only run scripts from these specific origins." Even if an attacker injects a <script>, CSP prevents the browser from executing it.

CSRF: making your browser the attacker

Same-origin policy stops origin A's JavaScript from reading origin B's responses. It does not stop origin A from making requests to origin B. A page on evil.com can make a POST request to bank.com/transfer, and the browser will cheerfully attach bank.com's session cookie to the request. If the bank trusts the cookie as the only authentication, the transfer goes through, even though the user only visited evil.com. This is cross-site request forgery (CSRF), and the standard defence is the CSRF token: the server puts a random, unguessable token in a hidden field of its form and rejects any submission that does not echo it back. Since evil.com cannot read bank.com's pages (same-origin policy), it cannot learn the token, and its forged submission fails.

Fig 12.10 · CSRF · the cookie attaches to the cross-site request unless we add a token
Fig 12.10 · CSRF · the cookie attaches to the cross-site request unless we add a token unguessable token in form ⇒ attacker can submit but cannot forge WITHOUT CSRF TOKEN: attack succeeds ① user logs into bank.com, browser stores session cookie ② user visits evil.com (in another tab, while bank session still valid) ③ evil.com runs: <form action="bank.com/transfer" method="POST">…</form> · auto-submit ④ browser sends POST with bank.com cookie attached ⑤ bank processes transfer · attacker won WITH CSRF TOKEN: attack fails ① bank.com renders form with hidden <input name="csrf" value="x9k2A…"> · token tied to session ② evil.com cannot read bank.com's HTML (same-origin policy blocks it) ③ evil.com submits a POST without the token (or with a guessed wrong token) ④ bank receives the POST, validates token against session: mismatch ⑤ bank rejects · 403 Forbidden · attack fails modern alternative: SameSite=Strict cookie attribute · cookie not sent on cross-site POST at all

CSRF tokens are an old defence (~2002); the modern complement is the SameSite cookie attribute, introduced in 2016 and defaulted to Lax by Chrome and Edge since 2020 (Firefox and Safari lean on third-party-cookie blocking instead, which is why prudent sites still set it explicitly). SameSite=Lax means: do not attach this cookie to cross-site POST requests; only to top-level navigations. SameSite=Strict goes further: do not attach the cookie even on cross-site GET. Together with CSRF tokens, they make the classical CSRF attack mostly obsolete on a properly configured site. The deeper lesson is that browser security has, over twenty years, moved from "defend at the application layer" (CSRF tokens, anti-XSS escaping) toward "defend at the platform layer" (SameSite, CSP, Trusted Types). The web's own primitives are getting safer; the application code matters less than it used to.

CSP: telling the browser which scripts you trust

The strongest mitigation in the modern web security stack is Content Security Policy (CSP), first shipped in Firefox 4 in 2011, a web standard since 2016 and supported by every major browser by the late 2010s. CSP is a response header (Content-Security-Policy: …) in which the server declares exactly which origins the browser may load resources from, separately for scripts, styles, images, frames, fonts, fetch destinations and every other resource type. A typical allowlist policy: script-src 'self' https://cdn.example.com; style-src 'self' means: "execute scripts only from the same origin and from cdn.example.com; load styles only from the same origin; reject other sources for those two." (Adding default-src 'none' closes every other kind of resource too.) If an attacker injects a <script src="https://evil.com/x.js">, the browser sees that evil.com is not in the policy and refuses to load the script. The policy admits no inline code either, so the inline <script> and onerror payloads of Fig 12.9 are refused too: the markup still lands in the page, but the browser never runs it.

Fig 12.11 · CSP · the server tells the browser which scripts to trust, the browser refuses the rest
Fig 12.11 · CSP · the server tells the browser which scripts to trust, the browser refuses the rest The server's response carries the header Content-Security-Policy: script-src 'self' https://cdn.example.com. The page then asks the browser to run four scripts, one at a time, and the browser checks each against the policy. The site's own /app.js runs, being from the same origin; cdn.example.com/lib.js runs, being listed; a script injected from other.example is refused, not being listed; and an injected inline script is refused, since the policy does not allow inline code. The injections landed in the page, yet neither runs. Content-Security-Policy: script-src 'self' https://cdn.example.com requested browser checks /app.js runs: own origin cdn.example.com/lib.js runs: listed other.example/x.js refused: unlisted inline <script> refused: inline each script checked on its own injected, yet refused

The browser checks each script against the header on its own: the page's own and the listed origin run, and everything else is refused, even when an XSS injection has already put it in the page. Inline code counts as everything else unless the policy admits it by nonce or hash.

How it works
  1. Nonces and hashes. A strict policy admits chosen inline scripts by a fresh random nonce, sent in the header and repeated on each trusted script tag, or by the hash of the script's text.
  2. Reports. A report-to directive names a reporting endpoint, and the browser sends it a report for each violation, so a refused injection shows up in the site's monitoring (the older report-uri still works).
  3. Rolling it out. Content-Security-Policy-Report-Only applies a policy without enforcing it and reports what it would have blocked, so a site can see what would break before switching it on.

Closing the chapter, closing the part

JavaScript was designed in ten days by one person under the wrong brief, given an unrelated marketing name to ride Java's hype, shipped before specification, and carried to the server in earnest by Node.js fourteen years after birth. The event loop it inherited from the early browser is now the dominant concurrency model on the modern web. The engines that run it are masterpieces of compiler engineering that exist only because the language is everywhere. The security mechanisms that surround it began with the same-origin policy, which shipped with the language itself; CSRF tokens, Content Security Policy and the rest were added one attack at a time over thirty years.

Part III is now complete. We started with voltage on a wire (Chapter 8), promoted it into routed packets (Chapter 9), built reliability on top of unreliability (Chapter 10), turned the network into a worldwide library of trustworthy documents (Chapter 11), and made those documents alive (Chapter 12). A reader who has followed all five chapters can now, in principle, read every layer of an HTTPS request, from voltage on copper to a piece of JavaScript modifying the DOM and rendering at 60 frames per second.

End of Part III

The network is built.

Part IV turns to where information lives at rest and how it is kept safe: the relational database, cryptography in mathematical depth, and the security chapter that ties every attack of Parts I to III into one working theory.

Continue to Part IV: Data and Security