Metningskontroll
og evolusjon
Hvordan tusenvis av TCP-forbindelser deler et begrenset nettverk uten å kollapse det — fra slow start til CUBIC og QUIC.
Hvorfor metningskontroll?
Flow control beskytter én mottaker. Metningskontroll beskytter det delte nettverket mellom alle sendere.
Tenk deg en motorvei der hver sjåfør bestemmer sin egen fart. Uten noen form for regulering — trafikklys, fartsgrenser, påkjøringsramper — vil veien til slutt gå i lås. Metningskontroll i TCP er trafikklyset: en mekanisme som lar sende ren justere farten sin basert på hvor fullt nettverket virker å være, uten at noen sentral myndighet styrer det.
To vinduer, én begrensning
TCP-senderen styres av to vinduer som begge legger begrensninger:
| Vindu | Hva det beskytter |
|---|---|
| rwnd | Receive window — satt av mottakeren i TCP-headeren. Beskytter mottakerens buffer mot overflyt. |
| cwnd | Congestion window — vedlikeholdt av senderen lokalt, usynlig i headeren. Beskytter nettverket mot overbelastning. |
Regelen er enkel:
LastByteSent − LastByteAcked ≤ min(cwnd, rwnd)
Senderen kan aldri ha mer data «in flight» enn den minste av de to vinduene. I praksis, når nettverket er flaskehalsen, er cwnd < rwnd, og det er cwnd som styrer.
TCPs senderrate blir da omtrent:
rate ≈ cwnd / RTT bytes/sek
rwnd annonseres i TCP-headeren og kan observeres i en pakkefangst. cwnd derimot eksisterer bare internt hos senderen — den er aldri synlig på tråden.
Bottleneck-lenken
Når TCP øker sendraten, er det til slutt én lenke i stien som blir mettet — bottleneck-lenken. Ruterens utgangskø fyller seg opp, og pakker begynner å gå tapt. Nøkkelinnsikten:
1. Å øke sendraten utover bottleneck-kapasiteten gir ikke høyere gjennomstrømning — det gir bare mer pakketap.
2. Å øke sendraten utover bottleneck-kapasiteten øker RTT, fordi pakker venter lenger i køen.
Målet: «Keep the end-end pipe just full, but not fuller.»
To tilnærminger til metningskontroll
Det finnes to fundamentalt forskjellige måter å angripe metningsproblemet på:
| Tilnærming | Hvordan det fungerer |
|---|---|
| End-to-end | Nettverket gir ingen eksplisitt tilbakemelding om metning. Senderen må selv utlede tilstanden — basert på observert tap (timeout eller dupliserte ACK-er) og forsinkelse. TCP bruker denne tilnærmingen. |
| Nettverksassistert | Rutere gir eksplisitt tilbakemelding til senderen om metningsnivået — for eksempel via spesielle kontrollmeldinger, eller ved å sette bits i pakkeheaderen som signaliserer at en lenke nærmer seg metning. |
Internettets TCP har tradisjonelt vært rent end-to-end: IP-laget gir ingen hjelp, og senderen er overlatt til seg selv. All informasjon om nettverkets tilstand må utledes indirekte — fra ACK-er som uteblir, fra forsinkelser som øker, eller fra pakker som forsvinner. Det er nettopp dette som gjør TCP-metningskontroll til et så elegant og utfordrende problem.
Eksponentiell opptrapping
Navnet er misvisende — slow start er faktisk den raskeste fasen av TCP. Vinduet dobles hver RTT.
Når en TCP-forbindelse nettopp er opprettet, vet senderen ingenting om nettverkets kapasitet. Å sende for mye umiddelbart kan forårsake metning. Å sende for lite kaster bort tid. Løsningen er å starte forsiktig og doble sendraten for hver RTT til man treffer en grense.
Mekanismen
1. Start med cwnd = 1 MSS.
2. For hver mottatte ACK, øk cwnd med 1 MSS: cwnd = cwnd + MSS.
3. Effekten: cwnd dobles per RTT (eksponentiell vekst).
Eksempel
Med MSS = 1500 bytes og RTT = 200 ms:
| RTT | cwnd | Segmenter sendt |
|---|---|---|
| 0 | 1 MSS | 1 |
| 1 | 2 MSS | 2 |
| 2 | 4 MSS | 4 |
| 3 | 8 MSS | 8 |
| 4 | 16 MSS | 16 |
Initiell rate: 1500 / 0.2 = 7.5 kB/s. Etter 4 RTT-er: 24 000 / 0.2 = 120 kB/s. Eksponentiell vekst rammer raskt opp til tilgjengelig kapasitet.
Når slutter slow start?
Slow start avsluttes ved ett av tre hendelser:
- Timeout — cwnd settes tilbake til 1 MSS,
ssthresh = cwnd/2, og slow start starter på nytt. - cwnd ≥ ssthresh — overgang til congestion avoidance (lineær vekst).
- 3 dupliserte ACKs — overgang til fast recovery.
ssthresh (slow start threshold) er en variabel som husker omtrent hvor det gikk galt sist. Den starter typisk på en høy verdi (f.eks. 64 KB) og halveres ved hvert tap. Den markerer grensen mellom eksponentiell og lineær vekst.
Under slow start med cwnd = 4 MSS sender TCP 4 segmenter og mottar 4 ACKs. Hva blir ny cwnd?
Additive increase, multiplicative decrease
Når cwnd har nådd ssthresh, skifter TCP fra eksponentiell til forsiktig lineær vekst — og halverer brutalt ved tap.
AIMD (Additive Increase, Multiplicative Decrease) er kjernen i TCPs metningskontroll. Ideen er vakker i sin asymmetri: voks sakte oppover, kutt raskt nedover. Denne asymmetrien gir stabilitet og rettferdighet — egenskaper som ren eksponentiell vekst aldri kunne gitt.
Additive increase
I congestion avoidance-fasen øker cwnd med 1 MSS per RTT — ikke per ACK som i slow start. Formelen per ACK er:
cwnd = cwnd + MSS × (MSS / cwnd)
Siden det kommer omtrent cwnd/MSS ACKs per RTT, gir dette en total økning på 1 MSS per RTT — lineær vekst.
Multiplicative decrease
Ved pakketap halverer TCP vinduet:
ssthresh = cwnd / 2
cwnd = ssthresh (ved 3 dup-ACKs, TCP Reno)
cwnd = 1 MSS (ved timeout, alle varianter)
Sagtann-mønsteret
Resultatet er det karakteristiske sagtannmønsteret: cwnd vokser lineært oppover, faller brått ved tap, vokser lineært igjen. TCP «sonderer» stadig etter mer båndbredde.
Regneeksempel: congestion avoidance
Anta cwnd = 3000 bytes og MSS = 1500 bytes. Det betyr 2 segmenter i flight, altså 2 ACKs per RTT.
ACK 1: cwnd = 3000 + 1500×(1500/3000) = 3750
ACK 2: cwnd = 3750 + 1500×(1500/3750) = 4350
Netto økning over hele RTT-en: omtrent 1 MSS (fra 3000 til ~4500). Lineær vekst, som forventet.
I congestion avoidance med cwnd = 10 MSS, hvor mye vokser cwnd på én RTT?
Reno vs. Tahoe
Ikke alle pakketap er like alvorlige. Tre dupliserte ACKs betyr at noe fortsatt kommer frem — og TCP kan reagere mildere.
Det er en vesentlig forskjell mellom en timeout og tre dupliserte ACKs. En timeout betyr: «Jeg har ikke hørt noe som helst på lenge — nettverket er kanskje helt nede.» Tre dupliserte ACKs betyr: «Ett segment gikk tapt, men andre segmenter kommer frem — for mottakeren sender jo ACKs.» TCP Reno utnytter denne forskjellen.
TCP Tahoe (den opprinnelige)
TCP Tahoe (1988) behandler alle tap likt:
ssthresh = cwnd / 2
cwnd = 1 MSS
Start slow start på nytt
TCP Reno (forbedringen)
TCP Reno (1990) differensierer:
ssthresh = cwnd / 2
cwnd = ssthresh + 3 MSS
Gå til fast recovery
ssthresh = cwnd / 2
cwnd = 1 MSS
Start slow start på nytt
Hva skjer i fast recovery?
Under fast recovery øker cwnd med 1 MSS for hver dupliserte ACK som ankommer (disse tyder på at etterfølgende segmenter kommer frem). Når en ny ACK ankommer (som bekrefter det retransmitterte segmentet), avsluttes fast recovery:
cwnd = ssthresh
Gå til congestion avoidance
Sammenligning
| Hendelse | TCP Tahoe | TCP Reno |
|---|---|---|
| Timeout | cwnd → 1 MSS, slow start | cwnd → 1 MSS, slow start |
| 3 dup-ACKs | cwnd → 1 MSS, slow start | cwnd → ssthresh+3, fast recovery |
Reno unngår den kostbare turen tilbake til 1 MSS ved dupliserte ACKs — noe som kan spare mange RTT-er med lav gjennomstrømning.
TCP Reno registrerer 3 dupliserte ACKs mens cwnd = 20 MSS. Hva skjer?
Raskere tilbake med kubisk funksjon
AIMD er rettferdig, men langsom. CUBIC bruker en smartere kurve for å utnytte kapasiteten raskere.
Klassisk TCP bruker AIMD: lineær økning med 1 MSS per RTT. På en lenke med høy kapasitet og lang forsinkelse kan det ta mange RTT-er å fylle lenken etter et tap. CUBIC stiller spørsmålet: kan vi gjøre det bedre?
Innsikten
La Wmax være sendraten da det siste tapet skjedde. Etter et tap vet vi at nettverket sannsynligvis ikke har endret seg mye. Da er det trygt å raskt nærme seg Wmax igjen — men vi bør være forsiktige nær Wmax, fordi det var der det gikk galt sist.
Kubisk vekst
CUBIC øker cwnd som en funksjon av tiden siden siste tap, ikke av RTT:
- Langt fra Wmax — store, raske økninger (kurven er bratt).
- Nær Wmax — forsiktige, små økninger (kurven flater ut).
- Forbi Wmax — sonderer forsiktig etter ny kapasitet (kurven stiger igjen).
Punktet K er tidspunktet der cwnd ville nå Wmax. K er tunbart. Kurven er symmetrisk rundt K — den ligner en kubisk funksjon (derav navnet).
Hva er den viktigste fordelen med CUBIC over klassisk AIMD?
Er TCP fair?
Hvis K TCP-sesjoner deler en flaskehals med kapasitet R, bør hver ideelt sett få R/K.
Rettferdighet er ikke bare et akademisk spørsmål — det handler om internettets grunnleggende funksjon. Hvis én forbindelse kunne ta alt, ville alle andre sulte. AIMD har en elegant egenskap: under idealiserte forhold konvergerer parallelle TCP-forbindelser mot lik fordeling.
AIMD-konvergens
Betrakt to TCP-sesjoner som deler en bottleneck med kapasitet R. Plott gjennomstrømningen til sesjon 1 på x-aksen og sesjon 2 på y-aksen:
A → B: begge sesjoner øker additivt (helning 45°) til de treffer kapasitetsgrensen.
B → C: begge halverer cwnd (trekker mot origo), men dette flytter punktet nærmere den grønne linjen.
Gjenta — punktet konvergerer mot lik fordeling.
Forutsetningene
Samme RTT — forbindelser med lavere RTT øker raskere og får mer båndbredde i praksis.
Fast antall sesjoner — i virkeligheten kommer og går forbindelser.
Bare congestion avoidance — beviset antar lineær vekst, ikke slow start.
Rettferdighet og UDP
UDP-applikasjoner (f.eks. videostrømming) deltar ikke i metningskontroll. De kan sende så fort de vil og tar dermed mer enn sin «rettferdige» andel. Det finnes ingen «internettpoliti» som håndhever rettferdighet.
Parallelle TCP-forbindelser
En nettleser som åpner 10 parallelle TCP-forbindelser til en server, får i praksis 10 × sin rettferdige andel sammenlignet med en applikasjon som bruker én forbindelse. Dette er en «lovlig» måte å jukse på — og det er utbredt.
Fra Tahoe til QUIC
TCP har utviklet seg i 40 år. Den nyeste evolusjonen flytter transportfunksjonaliteten opp til applikasjonslaget.
TCP og UDP har vært de to transportprotokollene i over fire tiår. I løpet av den tiden har TCP blitt forbedret gjentatte ganger — fra Tahoe via Reno til CUBIC. Men den mest radikale endringen er en helt ny tilnærming: QUIC, som bygger transportfunksjonalitet oppå UDP.
TCPs utvikling
| År | Milepæl |
|---|---|
| 1981 | RFC 793 — original TCP-spesifikasjon |
| 1988 | TCP Tahoe — slow start og congestion avoidance (Jacobson) |
| 1990 | TCP Reno — fast retransmit og fast recovery |
| 2005 | BIC TCP |
| 2008 | TCP CUBIC — kubisk vekstfunksjon, standard i Linux siden 2006 (RFC 8312 publisert 2018) |
Forskjellige scenarier, forskjellige utfordringer
| Scenario | Utfordring |
|---|---|
| Store overføringer | Mange pakker «in flight»; ett tap stenger hele pipelinen |
| Trådløst | Tap pga. støy forveksles med metning — TCP reduserer unødvendig |
| Lange forsinkelser | Ekstremt lang RTT gjør AIMD-oppramping smertefull |
| Datasentre | Latenskritiske tjenester der selv millisekunders kø-forsinkelse teller |
QUIC: Quick UDP Internet Connections
QUIC er en applikasjonslagsprotokoll bygd over UDP. Den implementerer pålitelighet, metningskontroll, kryptering og multipleksing — alt i brukerrommet, uten å vente på OS-oppdateringer. QUIC er grunnlaget for HTTP/3 og er standardisert i RFC 9000 (2021).
Forbindelsesetablering: 1-RTT og 0-RTT
Med TCP + TLS kreves det minst to serielle håndtrykk: først TCPs tre-veis håndtrykk (1 RTT), deretter TLS-håndtrykket (1 RTT til). Først etter 2 RTT kan applikasjonsdata sendes.
QUIC kombinerer transport- og krypterings-håndtrykket til én enkelt prosess. Klienten sender sin kryptografiske «hello» sammen med forbindelsesparametrene i første pakke, og serveren svarer med sin del — alt i 1 RTT.
Enda bedre: hvis klienten har koblet til serveren tidligere, kan den gjenbruke lagrede kryptografiske nøkler og sende applikasjonsdata allerede i første pakke — såkalt 0-RTT. Serveren kan begynne å behandle forespørselen uten å vente på et komplett håndtrykk.
Strømmer: førsteklasses multipleksing
En sentral innovasjon i QUIC er at strømmer (streams) er et innebygd konsept i selve transportprotokollen. Innenfor én enkelt QUIC-forbindelse kan det opprettes mange parallelle strømmer — uten nye håndtrykk. Hver strøm har:
- Egne sekvensnumre — uavhengig av de andre strømmene
- Egen pålitelig levering — tap og retransmisjon i én strøm påvirker ikke andre
- Egen flytkontroll — både per strøm og per forbindelse
I HTTP/3 tilsvarer hver strøm typisk én HTTP-forespørsel/respons. Nye strømmer kan opprettes med minimal overhead, noe som gjør multipleksing langt mer effektivt enn HTTP/2 over TCP.
QUIC og head-of-line blocking
Med TCP og HTTP/2 deler alle forespørsler én enkelt bytestrøm. Selv om HTTP/2 logisk multiplekser flere forespørsler, er de alle avhengige av TCPs pålitelige, ordnede levering. Hvis et segment tilhørende forespørsel 1 går tapt, må alle tre vente på retransmisjonen — selv om data for forespørsel 2 og 3 allerede er mottatt.
Connection ID og mobilitet
TCP identifiserer en forbindelse med en 4-tuppel: (kilde-IP, kilde-port, dest-IP, dest-port). Hvis en av disse endres — for eksempel når telefonen bytter fra Wi-Fi til mobilnett — må TCP-forbindelsen avsluttes og opprettes på nytt, inkludert nye håndtrykk.
QUIC bruker i stedet en connection ID i pakkene sine. Denne ID-en er uavhengig av de underliggende IP-adressene og portene. Når en klient bytter nettverk, holder connection ID-en forbindelsen i live — klienten fortsetter å sende pakker med samme ID fra sin nye adresse, og serveren gjenkjenner forbindelsen uten re-etablering.
Metningskontroll i QUIC
QUIC implementerer sin egen metningskontroll, separat fra operativsystemets TCP-stabel. Som standard bruker QUIC algoritmer som likner på TCP CUBIC, men siden QUIC kjører i brukerrommet (user space) kan metningskontrollalgoritmen oppdateres uavhengig av OS. Google har for eksempel eksperimentert med og rullet ut BBR (Bottleneck Bandwidth and RTT) i sin QUIC-implementasjon, helt uten å måtte oppdatere operativsystemer.
Oppsummering: QUICs fordeler over TCP
| Egenskap | TCP | QUIC |
|---|---|---|
| Forbindelsesetablering | TCP-håndtrykk + TLS-håndtrykk = 2 RTT | Kombinert håndtrykk = 1 RTT (0-RTT ved gjentilkobling) |
| HOL-blocking | Ett tapt segment blokkerer alle data | Per-strøm pålitelighet — tap blokkerer bare den berørte strømmen |
| Forbindelsesidentitet | 4-tuppel (IP + port) — brytes ved nettverksbytte | Connection ID — overlever nettverksbytte |
| Multipleksing | Én bytestrøm (HTTP/2 multiplekser logisk, men TCP ser bare én strøm) | Innebygde, uavhengige strømmer med egne sekvensnumre |
| Oppdaterbarhet | I OS-kjernen — krever kjerneoppgradering | I brukerrommet — oppdateres med applikasjonen |
| Kryptering | Valgfri (TLS er separat) | Alltid på — TLS 1.3 er innebygd i protokollen |
Hva er hovedfordelen med QUICs per-strøm pålitelighet?
Hvorfor kan en QUIC-forbindelse overleve at klienten bytter fra Wi-Fi til mobilnett?
Med dette er hele kapittel 3 dekket: fra transportlagets tjenester og UDP, gjennom TCPs pålitelige dataoverføring, til metningskontroll, CUBIC og QUIC.
cwnd gjennom fasene
Følg utviklingen av congestion window steg for steg — fra slow start, gjennom congestion avoidance, til tap og recovery.