Hvordan internett snakker: e-post, navn og sokler
En grundig gjennomgang av tre av applikasjonslagets viktigste byggesteiner — protokollene som flytter e-posten din, oversetter navn til adresser, og lar prosesser snakke sammen på tvers av jordkloden.
- Hvorfor SMTP fortsatt ser ut som en samtale fra 1982 — og hvorfor det er en god ting
- Hvordan DNS klarer å være både desentralisert og lynraskt på samme tid
- Forskjellen mellom å programmere mot UDP og TCP, og når du bør velge hva
- De gjennomgående temaene: push vs pull, tilstand vs tilstandsløs, kompleksitet i kanten
Den eldste meldingstjenesten på nettet
E-post fantes før weben, før Facebook, før alt egentlig. Når du forstår hvordan en e-post faktisk reiser fra deg til mottakeren, forstår du også hvorfor hele applikasjonslaget er bygd rundt et lite knippe gjentakende mønstre.
La oss begynne med et spørsmål som høres trivielt ut, men som ikke er det: når du trykker «Send» i Gmail, hva skjer egentlig? Du vet at meldingen havner hos mottakeren — men mellom «trykk» og «levert» ligger det et helt lite økosystem av programvare, og det er det økosystemet vi nå skal pakke ut.
E-postsystemet består av tre hovedkomponenter. Det er klientprogrammet du bruker for å lese og skrive — kalt user agent eller «mail reader». Det er e-postserverne som faktisk lagrer meldinger, både i en innboks for det som kommer inn og i en utgående kø for det som skal sendes. Og så er det SMTP, protokollen som binder serverne sammen og flytter meldinger mellom dem.
Det viktige å innse er at user agent-en aldri snakker direkte med mottakerens user agent. Posten din havner først hos din mailserver, som så åpner en SMTP-forbindelse mot mottakerens mailserver og leverer den. Mottakeren plukker den så opp fra sin egen server med en helt annen protokoll. Dette er ikke en tilfeldighet — det er et bevisst design som vi straks skal se hvorfor er smart.
Reisen til en e-post
Tenk deg at Alice vil sende en melding til bob@someschool.edu. Sekvensen er alltid den samme:
Legg merke til en fundamental asymmetri her: SMTP brukes kun mellom serverne (og fra Alice sin user agent inn til hennes egen server). Når Bob skal lese posten, bruker han ikke SMTP. Han bruker en «mail access protocol» — typisk IMAP, eller HTTPS dersom han bruker Gmail i nettleseren. Hvorfor? Fordi SMTP er en push-protokoll: avsenderen tar initiativet og dytter meldingen frem. Men når Bob skal hente post, vil han trekke meldinger ned fra sin egen server. Det er et helt annet bruksmønster, og det krever en helt annen protokoll.
SMTP — en samtale i klartekst
SMTP er en TCP-basert protokoll på port 25. Den er bemerkelsesverdig enkel: kommandoer sendes som ASCII-tekst, og serveren svarer med en tresifret statuskode etterfulgt av en kort tekst. Hele samtalen mellom to mailservere ser slik ut:
S: 220 hamburger.eduC: HELO crepes.frS: 250 Hello crepes.fr, pleased to meet youC: MAIL FROM: <alice@crepes.fr>S: 250 alice@crepes.fr... Sender okC: RCPT TO: <bob@hamburger.edu>S: 250 bob@hamburger.edu ... Recipient okC: DATAS: 354 Enter mail, end with "." on a line by itselfC: Do you like ketchup?C: How about pickles?C: .S: 250 Message accepted for deliveryC: QUITS: 221 hamburger.edu closing connection
Dette er hele protokollen i et nøtteskall. Tre faser: håndhilsing (HELO), overføring (MAIL FROM, RCPT TO, DATA), og avslutning (QUIT). Klienten — det er den sendende mailserveren her — skriver kommandoer, serveren svarer med statuskoder. 250 betyr OK, 354 betyr «fortsett», 220 og 221 er åpning og lukking. Akkurat som HTTP, men eldre.
En liten, men viktig detalj: legg merke til at meldingen avsluttes med en linje som bare inneholder ett enkelt punktum. Dette er SMTP sin måte å si «her slutter meldingen». Eller mer presist: serveren ser etter sekvensen CRLF.CRLF for å finne slutten på meldingsinnholdet. Og fordi alt skal være 7-bits ASCII, må alt binært (bilder, vedlegg, æøå) først kodes om til ASCII med MIME-koding.
SMTP vs HTTP — så like, og likevel så ulike
Det er fristende å se på SMTP og HTTP og tenke at de er nesten det samme. Begge bruker TCP. Begge har ASCII-kommandoer og statuskoder. Begge ble bygd i samme tidsperiode. Men forskjellene avslører to helt forskjellige designfilosofier:
| Egenskap | HTTP | SMTP |
|---|---|---|
| Initiativ | Pull (klienten ber) | Push (avsender dytter) |
| Innhold | Hver ressurs i sin egen respons | Flere objekter i én multipart-melding |
| Tegnsett | Binært tillatt | Krever 7-bits ASCII |
| Forbindelse | Persistent (HTTP/1.1+) | Persistent |
| Bruksområde | Hente sider/data på forespørsel | Levere meldinger asynkront |
Disse forskjellene er ikke vilkårlige. De følger direkte av problemene protokollene løser. Når du henter en nettside, er du der akkurat nå, og du venter på svaret. Når du sender e-post, skal mottakeren kanskje lese den om to dager — så meldingen må kunne legges i en kø, mellomlagres, og leveres når det passer.
Meldingsformatet — RFC 822
SMTP er protokollen som flytter meldingen, men selve meldingen — det som ligger inne i DATA-blokken — har sitt eget format definert i RFC 822. Sammenligningen er omtrent som mellom HTTP og HTML: HTTP frakter, HTML er det som fraktes.
En e-postmelding består av headerlinjer (To:, From:, Subject: og så videre), en blank linje, og en body. Det er viktig å skille disse headerne fra SMTP-kommandoene MAIL FROM og RCPT TO — sistnevnte er en del av forhandlingen mellom serverne (omtrent som en konvolutt), mens RFC 822-headerne er en del av selve brevet som ligger inne i konvolutten. De inneholder ofte de samme adressene, men trenger ikke å gjøre det. (Det er nettopp dette gapet noen typer phishing utnytter.)
Hvordan moderne webmail egentlig ser ut
I praksis ser ikke trafikken din ut akkurat som læreboken. Når du bruker Gmail eller Outlook på web, er det ikke SMTP du snakker fra nettleseren din ut til serveren — det er HTTPS. Først når Gmails servere skal levere meldingen videre til mottakerens server, kommer SMTP inn i bildet. Og når mottakeren — også på web — leser den, er det igjen HTTPS hele veien til nettleseren.
Hvordan internett finner ut hvor ting er
Mennesker husker navn. Datamaskiner ruter pakker etter tall. DNS er den enorme, distribuerte oversetter-maskinen som bygger broen mellom de to verdenene — og den må gjøre det milliarder av ganger hver dag, uten å bryte sammen.
Hver gang du skriver www.ntnu.no i nettleseren din, skjer det noe nesten umiddelbart som er så raskt at du aldri legger merke til det: et navn må oversettes til en IP-adresse før noe annet kan skje. Denne oversettelsen er tilsynelatende triviell — bare slå opp i en tabell — men den må fungere for hvert eneste navn på hele internett, og hvert eneste navn må kunne endres når noen flytter en tjeneste til en ny server. Dette er ikke trivielt.
Hvorfor ikke bare én stor tabell?
La oss starte med den naive løsningen, som er pedagogisk verdifull nettopp fordi den ikke fungerer. Hva om vi hadde én sentral server et sted i verden, med en gigantisk tabell over alle navn-til-IP-mappinger? Klienten spør, serveren svarer, ferdig.
Løsningen — og dette er kanskje den vakreste designideen i hele protokollverdenen — er å gjøre DNS distribuert og hierarkisk. Ingen enkelt server vet alt. I stedet vet hver server litt, og vet hvem den skal spørre om resten.
Hierarkiet
Tenk på navnet www.amazon.com. Hierarkiet bygges fra høyre mot venstre. Helt på toppen sitter root-serverne. De vet ikke noe om amazon, men de vet hvem som styrer .com. Under root sitter TLD-serverne (Top-Level Domain) — én gruppe for .com, én for .org, én for .no, og så videre. .com-serveren vet ikke IP-adressen til www.amazon.com, men den vet hvilken server som er autoritativ for amazon.com. Den autoritative serveren — som driftes av Amazon selv — vet til slutt det faktiske svaret.
Det er omtrent 13 logiske rot-«servere» i verden, men hver av dem er replikert hundrevis av ganger ved hjelp av en teknikk som heter anycast — over 1000 fysiske maskiner totalt. ICANN (Internet Corporation for Assigned Names and Numbers) administrerer rotsonen. Disse er kontaktpunktet for siste utvei: en lokal navneserver kontakter root-serverne kun når den ikke har funnet svaret andre steder.
Lokale navneservere — den glemte helten
Mellom deg og hele dette hierarkiet sitter det noen som ikke teknisk sett er en del av selve DNS-hierarkiet, men som likevel er den absolutt viktigste serveren for ytelsen din: din lokale navneserver. Hver internettleverandør driver en. NTNU har en. Bedriften din har en. Når maskinen din skal slå opp et navn, går spørsmålet alltid først hit.
Den lokale serveren har en lokal cache med navn den nylig har slått opp. Hvis svaret ligger i cachen, slipper du hele turen oppover hierarkiet — du får svaret tilbake på millisekunder. Dette er hvorfor DNS oppleves som umiddelbar selv om det egentlig kan involvere flere mellomledd. De fleste forespørsler stopper i cachen.
To måter å spørre på: iterativ vs rekursiv
Når en navneserver må gå videre i hierarkiet, finnes det to fundamentale strategier. La oss følge et eksempel: en maskin på engineering.nyu.edu vil vite IP-adressen til gaia.cs.umass.edu.
Det du så i animasjonen ovenfor er en iterativ oppslag: hver server svarer ikke med endelig svar, men med «jeg vet ikke, men spør denne i stedet». Den lokale serveren gjør alt arbeidet selv — den hopper fra root til TLD til autoritativ.
Den andre strategien er rekursiv: den lokale serveren spør root-serveren og sier «finn ut av dette for meg», og root-serveren tar selv ansvaret for å spørre TLD, som spør den autoritative, og svaret bobler tilbake gjennom hele kjeden. Dette er enklere å implementere på klientsiden, men legger en mye tyngre belastning på de øverste lagene av hierarkiet — og derfor brukes det typisk bare på den siste delen, mellom deg og din lokale server.
DNS-poster (Resource Records)
Hva er det egentlig som ligger lagret i alle disse navneserverne? Svaret er en distribuert database av resource records, eller RR-er. Hver post har formatet (name, value, type, ttl) — et navn, en verdi, en type, og en «time to live» som sier hvor lenge svaret er gyldig.
| Type | Hva «name» er | Hva «value» er |
|---|---|---|
| A | Et hostname | En IPv4-adresse |
| NS | Et domene (f.eks. amazon.com) | Hostname til en autoritativ navneserver |
| CNAME | Et alias-navn | Det «kanoniske» (egentlige) navnet |
| MX | Et domene | Hostname til en mailserver for det domenet |
A-posten er den klassiske: navn til IP. NS peker på hvilken server som er autoritativ. CNAME er et alias — derfor kan www.ibm.com egentlig være en peker til noe sånt som servereast.backup2.ibm.com, og du som bruker trenger aldri å vite det. MX er hvordan SMTP-serveren din finner ut hvor den skal levere e-post for et domene; uten MX-poster ville e-post ikke fungert.
Hvordan får man et nytt domene inn i DNS?
Tenk deg at du starter et nytt firma, «Network Utopia», og vil ha domenet networkutopia.com. Hvordan får du det inn i den globale DNS-databasen? Svaret er en registrar — en kommersielt autorisert organisasjon (akkreditert av ICANN) som lar deg registrere domenenavn.
Prosessen er omtrent slik: du går til en registrar (for eksempel GoDaddy eller Domeneshop), sjekker at networkutopia.com er ledig, betaler en avgift, og oppgir navn og IP-adresser til dine autoritative navneservere — la oss si dns1.networkutopia.com (212.44.9.129) og dns2.networkutopia.com (212.44.9.130). Registraren setter da inn to poster i .com-TLD-serverne:
(networkutopia.com, dns1.networkutopia.com, NS) (dns1.networkutopia.com, 212.44.9.129, A)
Nå vet .com-TLD-serverne at spørsmål om networkutopia.com skal sendes videre til dine autoritative servere. På disse serverne legger du selv inn A-poster for webserveren din (www.networkutopia.com → IP-adresse) og MX-poster for e-postserveren. Fra det øyeblikket er hele kjeden komplett: root → TLD → autoritativ → endelig svar.
Caching — den hemmelige sausen
Det vi egentlig ikke har sagt eksplisitt ennå er hvordan DNS klarer å være så raskt. Svaret er caching, overalt. Når en navneserver — hvilken som helst — lærer en mapping, lagrer den den i minnet sitt for fremtidige forespørsler. Den lagrer den til TTL utløper. TLD-server-adresser er typisk cachet i lokale servere stort sett alltid, så root-serverne blir nesten aldri kontaktet for vanlige oppslag.
Bakdelen er at cachede svar kan bli utdatert. Hvis du flytter en tjeneste til en ny IP-adresse, vil ikke endringen være synlig overalt på internett før alle relevante TTL-er har utløpt. Dette er hvorfor DNS-endringer ofte tar timer eller dager å «forplante seg» — det er ikke en feil, det er TTL-en som gjør jobben sin.
noen@example.com, men example.com ikke har noen MX-post i DNS?Mailserveren din vil først spørre etter MX-posten for example.com. Hvis den ikke finner noen, vil den i de fleste implementasjoner falle tilbake på å spørre etter en A-post for example.com direkte og prøve å levere til den IP-adressen. Dette er en historisk fallback fra tidlig SMTP. I praksis bør alle domener som mottar e-post ha MX-poster — uten dem er du avhengig av at avsendere implementerer fallbacken, og noen gjør ikke det.
DNS-meldinger
DNS bruker stort sett UDP på port 53 — det er en spørsmål-svar-protokoll der hvert spørsmål er kort nok til å passe i én UDP-pakke, og lavere overhead enn TCP gir mye bedre ytelse for noe som skjer milliarder av ganger om dagen. Både spørsmål og svar har samme meldingsformat: en header med en 16-bits identifikator og noen flagg, etterfulgt av seksjoner for spørsmål, svar, autoritative poster, og «hjelpsom tilleggsinformasjon».
Identifikatoren er viktig: når en server får mange svar tilbake, må den vite hvilket spørsmål hvert svar tilhører, og det er det dette tallet brukes til. Flagg-feltet sier blant annet om meldingen er et spørsmål eller svar, om rekursjon er ønsket, og om svaret kommer fra en autoritativ kilde.
DNS-sikkerhet
DNS er kritisk infrastruktur, og det betyr at noen vil angripe det. De vanligste angrepstypene er:
- DDoS — bombardere root- eller TLD-serverne med trafikk. Har vært forsøkt mange ganger; har hittil aldri klart å lamme internett, blant annet på grunn av caching og trafikkfiltrering.
- DNS-poisoning — sende falske svar til en navneserver slik at den cacher feil informasjon, og sender brukere til feil sted.
- DNS-amplifisering — sende DNS-spørsmål med forfalsket avsenderadresse, slik at svarene (som er mye større enn spørsmålene) sendes til offeret. Brukes som DDoS-våpen.
Forsvaret heter DNSSEC (RFC 4033), som legger digitale signaturer på DNS-svar slik at klienten kan verifisere at svaret faktisk kommer fra en autoritativ kilde. Det er ikke universelt utrullet ennå, men det er den langsiktige løsningen.
Når du selv skal skrive noe som snakker
Alle protokollene vi har sett på — SMTP, HTTP, DNS — er bygget oppå noe lavere: socket-API-et. Når du skal skrive ditt eget nettverksprogram, er det her du faktisk møter transportlaget ansikt til ansikt.
En sokkel er et merkelig ord for noe så fundamentalt. Tenk på den som en dør: på den ene siden står prosessen din, på den andre siden står transportlaget i operativsystemet. Du som programmerer kontrollerer det som skjer på innsiden av døren. Operativsystemet styrer alt som skjer på utsiden — pakkene, rutingen, retransmisjon. Sokkelen er kontrakten mellom dere to.
Det finnes to typer sokler, fordi det finnes to transporttjenester du kan bruke: UDP for upålitelige datagrammer, og TCP for pålitelig, ordnet bytestrøm. Forskjellen mellom dem er stor nok til at API-ene faktisk ser annerledes ut — du bruker forskjellige funksjoner for å sende og motta — og valget mellom dem er det første og kanskje viktigste designvalget du tar når du skriver et nettverksprogram.
Et lite eksempelprogram
Gjennom resten av denne delen skal vi bygge en helt enkel applikasjon, både i UDP-versjon og TCP-versjon, slik at forskjellen kommer tydelig frem. Applikasjonen er bevisst trivielt:
UDP — datagrammer i posten
Det første du må forstå om UDP er at det ikke finnes noen forbindelse. Det er ingen håndhilsing før data sendes. Hver pakke står på egen hånd. Avsenderen putter eksplisitt på destinasjons-IP og portnummer for hver eneste pakke, og mottakeren får ut avsenderens IP og port fra hver pakke som kommer inn. Det er som å sende postkort — hvert postkort er sin egen lille hendelse, og det finnes ingen samtale.
Konsekvensene er viktige: pakkene kan bli borte, og de kan komme i feil rekkefølge. Operativsystemet vil ikke gjøre noe med det. Hvis applikasjonen din krever pålitelighet, må du bygge det selv. Til gjengjeld er UDP ekstremt lett — ingen oppstartstid, ingen tilstand, ingen ekstra runtripp før første byte kan sendes.
La oss se på koden. Først klienten:
# Python UDP-klient from socket import * serverName = 'hostname' serverPort = 12000 # Lag en UDP-sokkel clientSocket = socket(AF_INET, SOCK_DGRAM) message = input('Skriv en setning: ') # Vi må selv legge ved adresse og port for hver send clientSocket.sendto(message.encode(), (serverName, serverPort)) # Les svar — vi får også vite hvem som svarte modifiedMessage, serverAddress = clientSocket.recvfrom(2048) print(modifiedMessage.decode()) clientSocket.close()
Legg merke til SOCK_DGRAM — det er det som sier «UDP, takk». Og legg merke til sendto: det andre argumentet er en tuple med servernavn og port. Vi må oppgi det hver eneste gang vi sender, fordi sokkelen ikke har noen forestilling om hvem den «er forbundet med». Tilsvarende returnerer recvfrom både dataene og avsenderens adresse — for hvert datagram kan teknisk sett komme fra en hvilken som helst avsender.
Serveren ser slik ut:
# Python UDP-server from socket import * serverPort = 12000 serverSocket = socket(AF_INET, SOCK_DGRAM) serverSocket.bind(('', serverPort)) # Lytt på port 12000 print("Serveren er klar.") while True: message, clientAddress = serverSocket.recvfrom(2048) modifiedMessage = message.decode().upper() serverSocket.sendto(modifiedMessage.encode(), clientAddress)
Serveren er nesten enklere enn klienten. Den lager en sokkel, binder den til en spesifikk port (12000), og går inn i en evig løkke. For hvert datagram som kommer inn, leser den både innholdet og klientens adresse — og bruker akkurat den adressen til å sende svaret tilbake. Det finnes ingen «forbindelse» som blir åpnet eller lukket. Hver iterasjon i løkken er en hel transaksjon i seg selv.
TCP — en samtale fra start til slutt
TCP er en helt annen verden. Før noen data kan flyte, må klienten opprette en forbindelse til serveren — det berømte trefoldige håndtrykket. Når forbindelsen er etablert, har du noe som oppfører seg som et rør: bytes du sender inn på den ene enden kommer ut i samme rekkefølge på den andre enden, garantert, selv om underliggende pakker ble mistet og måtte sendes på nytt.
Det fine designtrekket — og noe som kan være forvirrende første gang du møter det — er at serveren faktisk har to typer sokler. Den ene er en «velkomstsokkel» som sitter og venter på at klienter skal koble seg til. Når en klient kobler seg til, lager operativsystemet en helt ny sokkel — en «forbindelsessokkel» — som er dedikert til akkurat denne klienten. Velkomstsokkelen går tilbake til å vente på neste klient, og forbindelsessokkelen brukes til å snakke med klienten som nettopp koblet seg til.
Klientkoden:
# Python TCP-klient from socket import * serverName = 'servername' serverPort = 12000 clientSocket = socket(AF_INET, SOCK_STREAM) clientSocket.connect((serverName, serverPort)) # 3-veis håndtrykk skjer her sentence = input('Skriv en setning: ') # Ingen behov for å oppgi adresse — sokkelen vet det allerede clientSocket.send(sentence.encode()) modifiedSentence = clientSocket.recv(1024) print('Fra serveren:', modifiedSentence.decode()) clientSocket.close()
Forskjellene fra UDP-versjonen er små men avgjørende. SOCK_STREAM i stedet for SOCK_DGRAM. Den nye linjen connect() som ikke fantes i UDP — det er her selve TCP-forbindelsen blir opprettet. Og en gang det er gjort, bruker vi send og recv uten å oppgi noen adresse, fordi sokkelen vet hvem den er koblet til.
Serverkoden er der den interessante forskjellen virkelig blir tydelig:
# Python TCP-server from socket import * serverPort = 12000 serverSocket = socket(AF_INET, SOCK_STREAM) serverSocket.bind(('', serverPort)) serverSocket.listen(1) # Begynn å lytte etter forbindelser print("Serveren er klar.") while True: # accept() blokkerer til en klient kobler seg til, # og returnerer da en HELT NY sokkel for denne klienten connectionSocket, addr = serverSocket.accept() sentence = connectionSocket.recv(1024).decode() capitalizedSentence = sentence.upper() connectionSocket.send(capitalizedSentence.encode()) connectionSocket.close() # Lukk DENNE forbindelsen, ikke velkomstsokkelen
Se på serverSocket.accept(). Det er der det magiske skjer. Funksjonen blokkerer til en klient prøver å koble seg til, og når det skjer, returnerer den et par: en ny sokkel (connectionSocket) og adressen til klienten. Vi snakker med klienten gjennom denne nye sokkelen, og når vi er ferdige lukker vi kun den — velkomstsokkelen lever videre og venter på neste klient.
UDP og TCP — side om side
| UDP | TCP | |
|---|---|---|
| Sokkeltype | SOCK_DGRAM | SOCK_STREAM |
| Forbindelse | Ingen | Etableres med håndtrykk |
| Pålitelighet | Ingen — pakker kan mistes | Garantert levering, riktig rekkefølge |
| Sende | sendto(data, adresse) | send(data) |
| Motta | recvfrom() → data + adresse | recv() → data |
| Server | Én sokkel for alt | Én velkomst + én pr. klient |
| Egnet for | DNS, video, spill, telemetri | HTTP, SMTP, filoverføring |
Når velger man hva?
Det enkle svaret er: TCP for det meste. Det vanskelige svaret krever at du tenker på applikasjonens faktiske behov. Trenger du at hver eneste byte kommer frem? Bruk TCP. Tåler du tap, men trenger lav latenstid og kan ikke vente på retransmisjoner? Bruk UDP. Sender du noe som er så stort at TCP-forbindelsens overhead ikke betyr noe? TCP. Sender du små, hyppige, uavhengige beskjeder som DNS-oppslag eller video-pakker der en pakke som kommer 200 ms for sent like gjerne kan kastes? UDP.
De gjennomgående temaene
Tre forskjellige protokoller, tre forskjellige problemer — men hvis du leser dem ved siden av hverandre, ser du at de samme designvalgene dukker opp om og om igjen.
Det første temaet er sentralisert vs desentralisert. SMTP er desentralisert: hver organisasjon driver sin egen mailserver. DNS er hierarkisk, som er en form for desentralisering med koordinasjon — ingen vet alt, men strukturen sørger for at du alltid kan finne den som vet. Sentralisering er enkelt å forstå og umulig å skalere. Desentralisering er det motsatte: vanskelig å designe riktig, men eneste vei til de skalaene internett opererer på.
Det andre temaet er tilstandsløs vs tilstand. SMTP holder tilstand under en forbindelse (det er midt i en samtale). DNS er stort sett tilstandsløst — hvert spørsmål er uavhengig. UDP er fundamentalt tilstandsløst, TCP er fundamentalt tilstandsbevart. Tilstandsløse protokoller skalerer lettere fordi enhver server kan svare på ethvert spørsmål; tilstandsbevarte protokoller er nødvendige når noe må huskes på tvers av forespørsler.
Det tredje temaet er «kompleksitet i kanten». Dette er kanskje det mest karakteristiske ved internettarkitekturen i det hele tatt. Selve nettverket — rutere, kabler, IP — er bevisst dumt. All den interessante intelligensen sitter i endepunktene: i mailservere, i DNS-resolvere, i programmene du selv skriver med sokler. Dette designvalget er grunnen til at internett kunne vokse uten å måtte oppgraderes sentralt, og det er grunnen til at en student med en bærbar PC i Trondheim kan finne på en helt ny applikasjon i morgen og rulle den ut globalt uten å spørre noen om lov.
Det fjerde og siste temaet er pålitelig vs upålitelig meldingsoverføring — og innsikten at det ikke finnes ett riktig valg. TCP og UDP eksisterer side om side fordi de løser forskjellige problemer. Det samme gjelder oppover i stakken: SMTP er bygget på TCP fordi e-post må komme frem, mens DNS er bygget på UDP fordi raske oppslag som kan gjentas er bedre enn pålitelige oppslag som er trege.