Domain Name System
Ogni cosa che fai online comincia con una domanda che non vedi mai.
Prima che il browser scarichi una sola immagine, prima che la mail parta, prima che l'app sincronizzi qualunque cosa, qualcuno deve rispondere a una richiesta silenziosa: "questo nome, dove abita?". Tu scrivi centamori.com; la macchina ha bisogno di 172.66.147.243. In mezzo c'è un sistema che risponde a quella domanda miliardi di volte al secondo, in pochi millisecondi, con un'affidabilità che diamo per scontata fino al giorno in cui crolla — e quando crolla, mezza Internet sembra sparita.
Quel sistema è il DNS (Domain Name System). È la cosa più vicina che abbiamo a una rubrica telefonica del pianeta, solo che non esiste nessun elenco, nessun ufficio centrale, nessuno che la possiede. È un database distribuito su milioni di macchine, progettato nel 1983, che nessuno controlla per intero e di cui tutti si fidano.
In questo articolo lo smontiamo. Vedremo da dove viene, come è organizzato l'albero dei nomi, chi sono gli attori che si passano la domanda, e poi scenderemo fino ai byte: costruiremo una query DNS da zero, la spediremo sul filo, e leggeremo la risposta un byte alla volta.
Da un singolo file di testo
All'inizio non c'era nessun DNS. C'era un file.
Si chiamava HOSTS.TXT, ed era esattamente quello che sembra: un elenco di testo che associava ogni nome di host al suo indirizzo numerico. Lo manteneva a mano un singolo ente — lo Stanford Research Institute, l'SRI-NIC — e chiunque, su tutta la rete ARPANET, scaricava periodicamente quel file e se lo teneva in locale. Volevi raggiungere una macchina per nome? Il tuo computer cercava la riga corrispondente in HOSTS.TXT.
Per qualche centinaio di host funzionava. Poi la rete cominciò a crescere, e il modello si sgretolò sotto il suo stesso peso. Un solo file aggiornato a mano da una sola squadra; ogni nuova macchina che doveva essere comunicata al centro; ogni host del mondo che riscaricava l'intero elenco per stare al passo. Non era un problema di software, era un problema di forma: una struttura centralizzata e piatta non scala. Mai.
Jon Postel, una delle figure fondatrici di Internet, chiese a un ricercatore dell'ISI di immaginare qualcosa di diverso. Quel ricercatore era Paul Mockapetris, e nel 1983 pubblicò due documenti — RFC 882 e RFC 883 — che descrivevano un sistema di nomi distribuito, gerarchico, basato su due idee che reggono ancora oggi: delega e autorità. Scrisse anche la prima implementazione, un server chiamato Jeeves. Il 23 giugno 1983, insieme a Postel, eseguì il primo test del DNS.
Nel 1984 alcuni studenti di Berkeley scrissero BIND, l'implementazione che avrebbe dominato i decenni successivi. E nel novembre del 1987, dopo quattro anni di sperimentazione su una rete reale, Mockapetris riscrisse tutto in RFC 1034 e RFC 1035 — "Concepts and Facilities" e "Implementation and Specification". Quei due documenti sono ancora oggi la base su cui ogni implementazione DNS è costruita.
L'intuizione che cambiò tutto è questa: nessuno deve sapere tutto. Basta che ognuno sappia a chi chiedere.
L'albero rovesciato
Il DNS organizza i nomi come un albero capovolto, con la radice in cima.
. (root)
┌─────────┼─────────┐
com org it ← Top-Level Domains
│ │
centamori comune ← domini
│ │
www perugia ← sottodomini
Quando scrivi www.centamori.com lo leggi da sinistra a destra, ma il DNS lo legge al contrario: dalla radice (la parte più a destra, sottintesa), scende a com, poi a centamori, poi a www. In effetti il nome completo e corretto sarebbe www.centamori.com. — con un punto finale che rappresenta la radice. Quel punto c'è sempre, anche quando non lo scrivi.
Ogni livello dell'albero è governato da qualcuno di diverso, ed è qui che la delega diventa concreta:
- La radice è gestita da una manciata di organizzazioni sotto il coordinamento di IANA/ICANN. Sa una cosa sola: chi è responsabile di ciascun TLD.
- I TLD (
com,org,it, ....) sono gestiti dai registry. Il registry di `.com` non sa nulla del contenuto di `centamori.com`; sa solo a quali server chiedere. - Il dominio
centamori.comlo gestisci tu — o chi ospita la tua zona DNS. Qui vivono i record veri.
Questa porzione dell'albero affidata a una singola autorità si chiama zona. Il confine tra una zona e quella sottostante è una delega: il genitore non contiene i dati del figlio, contiene solo un puntatore — "per questo ramo, chiedi a quei server". Nessun nodo conosce l'intero albero. Ognuno conosce solo il proprio pezzo e i puntatori verso il livello sotto.
Gli attori
Come per ogni protocollo, prima di guardare i byte conviene sapere chi c'è sul palco. Una risoluzione DNS coinvolge quattro tipi di attori.
1) Lo stub resolver
È il pezzo di software dentro il tuo sistema operativo. Non sa risolvere nulla da solo: la sua unica abilità è girare la domanda a qualcun altro e aspettare la risposta. Quando il browser deve raggiungere un sito, è lo stub resolver che parte.
2) Il resolver ricorsivo
È il vero cavallo da lavoro. È il server a cui lo stub si rivolge — quello del tuo provider, oppure un resolver pubblico come 8.8.8.8 di Google o 1.1.1.1 di Cloudflare. Il resolver ricorsivo accetta la domanda e si assume l'intero onere di trovare la risposta, percorrendo l'albero per tuo conto. Soprattutto, mette in cache ciò che trova: è questa la ragione per cui il DNS non implode sotto il proprio traffico.
3) I server radice
Sono i tredici indirizzi (in realtà migliaia di macchine fisiche, distribuite in anycast su tutto il pianeta) che stanno in cima all'albero. Non conoscono il tuo dominio. Sanno solo indicare i server dei TLD.
4) I server autoritativi
Sono quelli che custodiscono i dati veri di una zona. Il server autoritativo di centamori.com è l'unico che può dire con certezza qual è l'indirizzo di quel dominio. Tutti gli altri, al massimo, ne tengono una copia in cache con una scadenza.
La risoluzione: una caccia al tesoro
Mettiamo che il resolver ricorsivo non abbia nulla in cache e debba risolvere www.centamori.com partendo da zero. Il percorso è una discesa lungo l'albero, una domanda per ogni livello:
- Chiede a un server radice: "chi gestisce
com?". La radice non conoscecentamori, ma risponde con un referral: "vai a chiedere ai server dicom". - Chiede a un server
.com: "chi gestiscecentamori.com?". Anche lui non conosce l'indirizzo finale, ma sa la delega: "ecco i server autoritativi percentamori.com". - Chiede al server autoritativo: "qual è l'indirizzo A di
www.centamori.com?". Questo lo sa, e risponde con l'indirizzo.
Tre domande, tre passi lungo l'albero. Da notare la differenza tra le due parole che si confondono sempre: la ricorsione è ciò che fa il resolver per te — "risolvimi tutto e torna con la risposta finale"; l'iterazione è ciò che il resolver fa verso i server della gerarchia, che non risolvono per suo conto ma rispondono solo con il rinvio al livello successivo.
Il secondo gradino di questa scala possiamo vederlo direttamente. Chiediamo a un resolver quali sono i server autoritativi per il TLD com:
com NS a.gtld-servers.net TTL 21600
com NS b.gtld-servers.net TTL 21600
com NS c.gtld-servers.net TTL 21600
...
com NS m.gtld-servers.net TTL 21600
Tredici server, da a a m, che reggono l'intero spazio dei nomi .com. È a uno di loro che ogni risoluzione di un dominio .com deve passare — a meno che, e qui sta il trucco, la risposta non sia già in cache da qualche parte lungo la catena.
Il TTL, ovvero perché tutto questo funziona
Se ogni visita a ogni sito facesse davvero il giro completo fino ai server radice, il DNS sarebbe collassato decenni fa. Non succede grazie a un numero attaccato a ogni risposta: il TTL (Time To Live), i secondi per cui quella risposta può essere conservata in cache prima di essere considerata scaduta.
Quel TTL 21600 significa che i server .com possono restare in cache sei ore. L'indirizzo di un grande sito può avere un TTL di pochi minuti — per poter cambiare server in fretta — mentre i record raramente toccati possono durare un giorno. Il TTL è il compromesso costante del DNS tra freschezza e carico: più è alto, meno traffico, ma più lente le modifiche a propagarsi. È per questo che quando sposti un sito ti dicono di "aspettare la propagazione DNS": stai semplicemente aspettando che le cache di mezzo mondo lascino scadere il vecchio valore.
Il protocollo sul filo
Qui il DNS si allontana da un protocollo come SMTP. SMTP è testo: puoi leggerlo con gli occhi. Il DNS è binario, compatto, pensato per stare in un singolo pacchetto UDP e tornare indietro nel minor numero di byte possibile. Non lo leggi: lo decodifichi.
Un messaggio DNS ha una struttura fissa, identica per domande e risposte, divisa in cinque parti:
| Sezione | Contenuto |
|---|---|
| Header | 12 byte: ID, flag, e quattro contatori |
| Question | La domanda: nome, tipo di record, classe |
| Answer | I record di risposta |
| Authority | I server autoritativi (usati nei referral) |
| Additional | Dati extra, come gli indirizzi dei server citati sopra |
L'header è dodici byte e non uno di più. I primi due sono un identificativo casuale che lega la risposta alla domanda. I due successivi sono i flag — un bit dice se è una domanda o una risposta, un altro chiede la ricorsione, altri ancora portano il codice di errore. Gli ultimi otto byte sono quattro contatori a 16 bit: quante domande, quante risposte, quanti record di autorità, quanti aggiuntivi.
Costruire una query da zero
Il modo migliore per capire un formato binario è generarlo a mano. Costruiamo in PHP una query per l'indirizzo di example.com, senza librerie, scrivendo i byte uno per uno.
<?php
function build_query(string $domain, int $id): string {
$flags = 0x0100;
$header = pack('nnnnnn', $id, $flags, 1, 0, 0, 0);
$question = '';
foreach (explode('.', $domain) as $label) {
$question .= chr(strlen($label)) . $label;
}
$question .= "\x00";
$question .= pack('nn', 1, 1);
return $header . $question;
}
Due dettagli meritano attenzione. Il flag 0x0100 ha un solo bit acceso, quello della recursion desired: stiamo chiedendo a un resolver ricorsivo di fare il lavoro per noi. E il nome non viene scritto come example.com: ogni etichetta è preceduta dalla sua lunghezza. example diventa il byte 7 seguito dai sette caratteri, com diventa 3 seguito da tre caratteri, e uno zero finale segna la radice. I due pack('nn', 1, 1) finali dichiarano che vogliamo un record di tipo A (valore 1), classe IN, Internet (valore 1).
Eseguendolo e stampando il pacchetto in esadecimale otteniamo 29 byte esatti:
fc12 0100 0001 0000 0000 0000
07 65786d61706c65 03 636f6d 00
0001 0001
E ogni byte è leggibile, se sai cosa cercare:
fc12— l'ID casuale della richiesta.0100— i flag: recursion desired.0001— una domanda. I tre contatori successivi sono a zero: nessuna risposta, è una query.07 example 03 com 00— il nome, etichetta per etichetta, chiuso dalla radice.0001 0001— tipo A, classe IN.
Spedirlo e leggere la risposta
Apriamo un socket UDP verso un resolver pubblico sulla porta 53, scriviamo il pacchetto, leggiamo la risposta. Poi la parte interessante: interpretare i byte che tornano indietro.
$sock = fsockopen('udp://8.8.8.8', 53, $errno, $errstr, 3);
fwrite($sock, build_query('example.com', random_int(0, 0xFFFF)));
$response = fread($sock, 512);
fclose($sock);
$header = unpack('nid/nflags/nqd/nan/nns/nar', substr($response, 0, 12));
echo "Risposte: {$header['an']}\n";
Risposte: 2
A 172.66.147.243 TTL 300
A 104.20.23.154 TTL 300
Due indirizzi per example.com, ognuno valido per 300 secondi. Il resolver ha percorso l'albero per noi — radice, .com, autoritativo — e ci ha restituito il risultato, probabilmente già pronto in cache.
Il puntatore nascosto
C'è un'eleganza nel formato che vale la pena svelare. Nella risposta, il nome del dominio comparirebbe due volte: una nella domanda, una in ogni record. Ripeterlo sarebbe uno spreco di byte preziosi in un pacchetto che vuole stare sotto i 512. Così il DNS usa la compressione dei nomi: invece di riscrivere example.com, un record può dire "il nome è là, all'offset X del pacchetto", con un puntatore di due byte riconoscibile perché i suoi primi due bit sono a uno.
if (($len & 0xC0) === 0xC0) {
$pointer = (($len & 0x3F) << 8) | ord($msg[$offset + 1]);
return read_name($msg, $pointer);
}
È un dettaglio minuscolo, ma è la differenza tra un protocollo progettato e uno improvvisato. Ogni byte conta, e il formato lo sa.
UDP, e quando non basta
Il DNS vive su UDP perché una risoluzione deve essere veloce e leggera: un pacchetto fuori, uno dentro, niente handshake. Il prezzo storico è stato un limite di 512 byte per risposta. Quando una risposta non ci sta — molti record, oppure una firma DNSSEC — entrano in gioco due meccanismi: EDNS0, che negozia pacchetti UDP più grandi, e il fallback su TCP, dove la dimensione non è più un problema. La regola pratica resta: UDP per la stragrande maggioranza delle query, TCP quando la risposta è troppo grande per un singolo pacchetto.
Lo zoo dei record
Il DNS non traduce solo nomi in indirizzi. Ogni nome può avere associati record di tipo diverso, ognuno con uno scopo. I più importanti:
A indirizzo IPv4 example.com -> 172.66.147.243
AAAA indirizzo IPv6 google.com -> 2607:f8b0:4001:c05::8b
CNAME alias verso un altro nome www.github.com -> github.com
MX server di posta, con priorità gmail.com -> 5 gmail-smtp-in.l.google.com
NS server autoritativi della zona com -> a.gtld-servers.net
TXT testo libero (SPF, DKIM, verifiche) v=spf1 include:...
SOA parametri della zona (TTL, serial) ...
Tre di questi raccontano qualcosa. Il CNAME è un alias puro: chiedi www.github.com e il DNS risponde "in realtà è github.com, riparti da lì" — ed è esattamente ciò che succede sul filo, una risposta che contiene prima il CNAME e poi l'indirizzo del nome a cui rimanda.
L'MX è il ponte verso un altro protocollo. Quando un server di posta deve consegnare un'email, è il record MX che cerca, non l'A. La risposta per gmail.com arriva con le priorità:
gmail.com MX 5 gmail-smtp-in.l.google.com
gmail.com MX 10 alt1.gmail-smtp-in.l.google.com
gmail.com MX 20 alt2.gmail-smtp-in.l.google.com
Il numero più basso ha la precedenza; gli altri sono i fallback. È la stessa ridondanza che, in un altro articolo, ho descritto come uno dei motivi per cui le email sono così resilienti — e il DNS è ciò che la rende possibile.
Il TXT, infine, è il sacco dove l'ecosistema ha infilato tutto ciò che serviva senza inventare nuovi formati: le politiche anti-spoofing della posta (SPF, DKIM, DMARC) vivono qui, come stringhe di testo dentro il DNS. Un protocollo nato per tradurre nomi è diventato, col tempo, anche il luogo dove si dichiara chi può parlare a nome di chi.
Il difetto gigantesco
Se hai seguito fino a qui, una domanda dovrebbe essere già emersa. Tutto questo viaggia su UDP, in chiaro, senza nessuna firma. Quando il resolver riceve una risposta, come fa a sapere che viene davvero da chi dice di venire?
La risposta, nel DNS originale, è scomoda: non lo sa.
L'unica difesa del protocollo del 1983 era quell'ID casuale a 16 bit nell'header, più la corrispondenza della porta. Se un attaccante riusciva a indovinarli e a spedire una risposta falsa prima di quella vera, il resolver l'avrebbe accettata — e, peggio, l'avrebbe messa in cache, servendo a tutti i suoi utenti un indirizzo fasullo. È il cache poisoning, e nel 2008 Dan Kaminsky dimostrò che era molto più facile da realizzare di quanto si pensasse. Per un istante, l'intera Internet ebbe paura.
E c'è un secondo problema, più silenzioso: la privacy. Ogni nome che risolvi viaggia in chiaro. Il tuo provider, chiunque condivida il wifi del bar, chiunque stia sul percorso, vede l'elenco completo dei siti che visiti — anche quando poi la connessione vera è cifrata in HTTPS. La busta è blindata, ma l'indirizzo sulla busta è scritto a pennarello.
Come per SMTP, la soluzione non è stata riscrivere il protocollo, ma costruirci intorno delle sovrastrutture:
- DNSSEC affronta l'autenticità. Ogni record viene firmato crittograficamente, e le firme formano una catena di fiducia che risale fino alla radice: l'autoritativo firma i suoi record, il TLD firma l'autoritativo, la radice firma il TLD. Se anche un solo anello non torna, la risposta è considerata manomessa. Non cifra nulla — chiunque legge ancora le tue query — ma garantisce che la risposta non sia falsa.
- DNS over TLS (DoT) e DNS over HTTPS (DoH) affrontano la privacy, cifrando il trasporto tra te e il resolver. Chi sta in mezzo non vede più i nomi che chiedi. La contropartita, dibattuta, è la centralizzazione: incanalare le query DNS dentro HTTPS verso pochi grandi resolver sposta una grande quantità di conoscenza su chi visita cosa nelle mani di pochi.
Nessuna di queste è universale, e l'adozione è ancora parziale. Ma la direzione è chiara: trasformare un protocollo nato in un'epoca di fiducia implicita in qualcosa che possa sopravvivere in un'epoca che di fiducia non ne ha più.
Il sostrato silenzioso
Il DNS è il pezzo di Internet a cui pensi di meno e da cui dipendi di più.
È nato da un file di testo che non scalava, ed è diventato uno dei più grandi sistemi distribuiti mai costruiti — senza un centro, senza un proprietario, senza una singola macchina che debba sapere tutto. La sua eleganza sta tutta in quell'idea che Mockapetris ebbe nel 1983: spezzare un problema impossibile (conoscere ogni nome del mondo) in una catena di problemi banali (sapere a chi chiedere). Ogni nodo è ignorante quasi di tutto, e proprio per questo l'insieme funziona.
La prossima volta che un sito si apre prima ancora che tu abbia finito di premere invio, ricorda che dietro quel nulla apparente c'è stata una piccola caccia al tesoro lungo un albero rovesciato, conclusa in qualche millisecondo da un sistema che ha imparato i nomi quando i computer collegati al mondo si contavano a centinaia.
I sistemi che durano non sono quelli più intelligenti. Sono quelli che hanno indovinato la forma giusta — e poi hanno avuto il buon senso di non toccarla.