Salta al contenuto
Note per Studenti DNS, proxy web, HTTP CONNECT e gateway applicativi

DNS, proxy web, HTTP CONNECT e gateway applicativi

In questa pagina 8

HTTP non lavora da solo. Prima di aprire la connessione serve l'indirizzo del server (DNS); fra il client e il server possono trovarsi intermediari (proxy, cache, gateway) che inoltrano, filtrano o traducono i messaggi; e per passare attraverso un proxy con un protocollo cifrato come HTTPS serve il metodo CONNECT. Questa nota tratta questi servizi di supporto dal punto di vista di chi li programma. L'architettura gerarchica del DNS e la sua amministrazione sono in Livello applicazione - DNSIl DNS (Domain Name System) traduce i nomi (www.amazon.com) negli indirizzi IP, perché le persone preferiscono i nomi e i protocolli TCP/IP usano gli indirizzi. È un database distribuito e gerarchico: albero rovesciato con radice, domini di primo livello e sottodomini (al più 128 livelli); le informazioni sono su tanti server (13 server radice) e i nuovi domini si registrano presso un registrar accreditato ICANN. Ogni ISP ha un DNS locale, il cui indirizzo l'host riceve con DHCP: l'host (resolver) gli manda la richiesta, di solito su UDP, e il DNS locale interroga radice, dominio di primo livello e server dell'organizzazione. Ogni server che impara un'associazione la tiene in cache, marcando la risposta non autoritativa, e la scarta dopo il TTL. Record (nome, tipo, valore, classe, TTL). Con il NAT il DNS deve restituire l'indirizzo pubblico. Quattro attacchi: macchina compromessa, risposta falsa all'host, avvelenamento della cache del DNS locale, server DNS malevolo.Livello applicazione - DNS →; la cache lato HTTP in Caching, autenticazione, tipi MIME e URI in HTTPLa cache HTTP riusa le risposte senza contattare il server finche' sono fresche (Cache-Control: max-age, Expires; in mancanza una durata euristica pari a circa il 10% del tempo trascorso da Last-Modified) e, quando sono scadute, le rivalida con una richiesta condizionale (If-None-Match con ETag, If-Modified-Since con Last-Modified) alla quale il server risponde 304 senza corpo; no-store vieta di memorizzare, no-cache obbliga a rivalidare, private esclude le cache condivise. L'autenticazione usa 401 con WWW-Authenticate e la risposta Authorization: Basic (base64 di utente:password, solo sopra TLS) o Digest (hash con nonce); 407 vale per i proxy. Content-Type porta il tipo MIME (tipo/sottotipo; parametri come charset e boundary), Accept e gli altri header Accept-* guidano la negoziazione. Un URI (RFC 3986) e' scheme://userinfo@host:porta/percorso?query#frammento, con percent-encoding %HH per i byte non ammessi e regole per risolvere i riferimenti relativi.Caching, autenticazione, tipi MIME e URI in HTTP →.

Il DNS dal punto di vista del programma

Come un programma risolve un nome

Un programma non parla con il DNS direttamente: chiama getaddrinfo (System call POSIX, file descriptor e API delle socketLe system call sono le funzioni con cui un programma in user space chiede servizi al kernel (open, read, write, close, fork, pipe, dup2, socket, bind, listen, accept, connect); restituiscono -1 e impostano errno in caso di errore; un file descriptor e' un intero che indicizza la tabella dei file aperti del processo (0 stdin, 1 stdout, 2 stderr) e vale per file, pipe e socket; read e write possono trasferire MENO byte del richiesto (e sui socket TCP non c'e' nessun confine fra i messaggi), quindi servono cicli write_all e read_exact; l'API delle socket crea un punto finale di comunicazione (socket), lo lega a indirizzo e porta (bind), lo rende passivo (listen), accetta connessioni (accept) o si connette (connect); getaddrinfo traduce nomi e porte in indirizzi (IPv4 e IPv6); UDP usa sendto e recvfrom e conserva i confini dei datagrammi, TCP e' uno stream affidabile.System call POSIX, file descriptor e API delle socket →). La libreria consulta, nell'ordine configurato (/etc/nsswitch.conf), il file locale /etc/hosts e poi il DNS, inviando le domande al resolver ricorsivo indicato in /etc/resolv.conf (di solito quello del provider o della rete locale). Il resolver ricorsivo lavora per conto del client: se non ha la risposta in cache interroga in modo iterativo i server radice, quelli del dominio di primo livello e quelli autoritativi (Livello applicazione - DNSIl DNS (Domain Name System) traduce i nomi (www.amazon.com) negli indirizzi IP, perché le persone preferiscono i nomi e i protocolli TCP/IP usano gli indirizzi. È un database distribuito e gerarchico: albero rovesciato con radice, domini di primo livello e sottodomini (al più 128 livelli); le informazioni sono su tanti server (13 server radice) e i nuovi domini si registrano presso un registrar accreditato ICANN. Ogni ISP ha un DNS locale, il cui indirizzo l'host riceve con DHCP: l'host (resolver) gli manda la richiesta, di solito su UDP, e il DNS locale interroga radice, dominio di primo livello e server dell'organizzazione. Ogni server che impara un'associazione la tiene in cache, marcando la risposta non autoritativa, e la scarta dopo il TTL. Record (nome, tipo, valore, classe, TTL). Con il NAT il DNS deve restituire l'indirizzo pubblico. Quattro attacchi: macchina compromessa, risposta falsa all'host, avvelenamento della cache del DNS locale, server DNS malevolo.Livello applicazione - DNS →), e restituisce l'indirizzo. Il client dunque fa una sola domanda al suo resolver.

Strumenti per osservare: dig www.example.com (mostra header, sezioni e TTL), dig +trace (segue la gerarchia dalla radice), nslookup, host, getent hosts nome.

Il formato dei messaggi (RFC 1035)

Domande e risposte hanno lo stesso formato:

 +---------------------+
 |       Header        |  12 byte
 +---------------------+
 |      Question       |  la domanda (QDCOUNT voci)
 +---------------------+
 |       Answer        |  record di risposta (ANCOUNT)
 +---------------------+
 |      Authority      |  server autoritativi (NSCOUNT)
 +---------------------+
 |     Additional      |  informazioni extra (ARCOUNT)
 +---------------------+

Header (12 byte, tutti i campi in big endian, Richiami di C per la programmazione di rete - memoria, puntatori, struct ed endiannessIn C un programma di rete maneggia byte, non oggetti: un processo ha codice, dati statici, heap e stack; i tipi hanno dimensioni fisse solo se si usano <stdint.h> (uint8_t, uint16_t, uint32_t); i dati che arrivano da un socket sono un buffer di byte con una lunghezza, NON una stringa C terminata da '\0'; i puntatori e l'aritmetica dei puntatori (buf + totale) permettono di riempire un buffer a pezzi; una struct puo' contenere byte di riempimento (padding) per l'allineamento, quindi non si spedisce con write(&s, sizeof s); sulla rete i numeri a piu' byte viaggiano in big endian (network byte order) e si convertono con htons, htonl, ntohs, ntohl, oppure si serializzano a mano con shift e maschere.Richiami di C per la programmazione di rete - memoria, puntatori, struct ed endianness →):

campo bit significato
ID 16 identificatore scelto dal client; la risposta lo ripete
flag 16 QR (bit 15: 0 domanda, 1 risposta), Opcode (4 bit), AA (risposta autoritativa), TC (troncata), RD (ricorsione desiderata), RA (ricorsione disponibile), Z, RCODE (4 bit)
QDCOUNT, ANCOUNT, NSCOUNT, ARCOUNT 16 ciascuno numero di voci nelle quattro sezioni

Codici RCODE: 0 NOERROR, 1 FORMERR (messaggio malformato), 2 SERVFAIL (guasto del server), 3 NXDOMAIN (il nome non esiste), 4 NOTIMP, 5 REFUSED.

Domanda: QNAME, QTYPE, QCLASS. Il nome è scritto come sequenza di etichette, ciascuna preceduta da un byte con la sua lunghezza (massimo 63), terminata da un byte 0 (la radice): www.example.com diventa 03 'w' 'w' 'w' 07 'e' 'x' 'a' 'm' 'p' 'l' 'e' 03 'c' 'o' 'm' 00, cioè 17 byte. QTYPE dice che record si cerca, QCLASS è quasi sempre 1 (IN, Internet).

Record di risorsa (Resource Record, RR), usato nelle sezioni di risposta: NAME, TYPE (2 byte), CLASS (2), TTL (4 byte, secondi di validità in cache), RDLENGTH (2), RDATA (i dati, RDLENGTH byte).

Compressione dei nomi. Un nome già comparso nel messaggio non si ripete: un byte che inizia con i bit 11 indica un puntatore a 14 bit (i 6 bit restanti più il byte seguente) all'offset, contato dall'inizio del messaggio, dove il nome continua. Per esempio c0 0c significa "il nome si trova all'offset 12", cioè subito dopo l'header, dove sta il nome della domanda. Chi legge un nome deve seguire i puntatori (e proteggersi dai cicli: un puntatore a sé stesso).

Esempio reale. La domanda per l'indirizzo di www.example.com (tipo A, ID = 0x1234) è di 33 byte:

12 34            ID
01 00            flag: QR=0, Opcode=0, RD=1
00 01 00 00 00 00 00 00      QDCOUNT=1, ANCOUNT=NSCOUNT=ARCOUNT=0
03 77 77 77 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00      QNAME  www.example.com (17 byte)
00 01            QTYPE  = A
00 01            QCLASS = IN

e la risposta è di 65 byte:

12 34  81 80  00 01 00 02 00 00 00 00        header: QR=1 RD=1 RA=1 RCODE=0, 1 domanda, 2 risposte
03 77 77 77 07 ... 63 6f 6d 00  00 01 00 01  la domanda ripetuta (21 byte)
c0 0c  00 01  00 01  00 00 01 17  00 04  68 14 17 9a     risposta 1: nome = puntatore a 12; A; IN; TTL = 0x117 = 279 s;
                                                          4 byte: 104.20.23.154
c0 0c  00 01  00 01  00 00 01 17  00 04  ac 42 93 f3     risposta 2: ... 172.66.147.243

Ogni risposta occupa 16 byte (2 di puntatore, 2 di tipo, 2 di classe, 4 di TTL, 2 di lunghezza, 4 di dato): 12 + 21 + 16 + 16 = 65. Due record A per lo stesso nome sono normali: il DNS è anche un modo per distribuire il carico (round robin) e per indirizzare l'utente al server più vicino (CDN).

Trasporto. Le domande usano UDP, porta 53: un datagramma di domanda, uno di risposta, senza handshake. Il limite classico di una risposta è di 512 byte; se non entra, il server la tronca e accende il bit TC: il client ripete la domanda su TCP porta 53 (con due byte di lunghezza davanti al messaggio). Estensioni moderne: EDNS0 per messaggi più lunghi su UDP, DNSSEC per le firme, DNS su TLS e su HTTPS. Poiché UDP non protegge, l'ID a 16 bit e la porta sorgente casuale sono le difese contro le risposte false (cache poisoning, Livello applicazione - DNSIl DNS (Domain Name System) traduce i nomi (www.amazon.com) negli indirizzi IP, perché le persone preferiscono i nomi e i protocolli TCP/IP usano gli indirizzi. È un database distribuito e gerarchico: albero rovesciato con radice, domini di primo livello e sottodomini (al più 128 livelli); le informazioni sono su tanti server (13 server radice) e i nuovi domini si registrano presso un registrar accreditato ICANN. Ogni ISP ha un DNS locale, il cui indirizzo l'host riceve con DHCP: l'host (resolver) gli manda la richiesta, di solito su UDP, e il DNS locale interroga radice, dominio di primo livello e server dell'organizzazione. Ogni server che impara un'associazione la tiene in cache, marcando la risposta non autoritativa, e la scarta dopo il TTL. Record (nome, tipo, valore, classe, TTL). Con il NAT il DNS deve restituire l'indirizzo pubblico. Quattro attacchi: macchina compromessa, risposta falsa all'host, avvelenamento della cache del DNS locale, server DNS malevolo.Livello applicazione - DNS →).

Un client DNS in C, con la domanda costruita a mano e la risposta decodificata, è in Esercizio - Client DNS con query e risposta costruite a mano (sul modello della prova pratica).

Web proxy

Definizione (proxy e gateway, RFC 9110 par. 3.7). Un intermediario è un nodo che sta fra client e server e inoltra i messaggi. Un proxy è un intermediario scelto dal client (configurato nel browser o nel sistema) che inoltra le richieste verso i server di origine. Un gateway, o reverse proxy, è un intermediario scelto dal server: per il client sembra il server di origine e inoltra le richieste ad altri sistemi.

tipo chi lo sceglie il client sa di usarlo? uso
forward proxy il client (configurazione) sì filtraggio, cache, anonimato, accesso controllato a Internet
intercepting (transparent) proxy la rete (reindirizzamento del traffico) no cache e filtri imposti
reverse proxy / gateway il gestore del sito no bilanciamento, TLS, cache, sicurezza
tunnel il client, con CONNECT sì far passare protocolli cifrati

Come funziona un forward proxy

Il client apre la connessione verso il proxy (non verso il server) e scrive nella request line l'URI assoluto (RFC 9112 par. 3.2.2):

http
GET http://www.example.com/index.html HTTP/1.1
Host: www.example.com
Proxy-Connection: keep-alive
User-Agent: curl/8.0

Il proxy ricava l'host e la porta dall'URI, apre una connessione TCP verso il server di origine (usando il DNS), riscrive la richiesta in forma normale (GET /index.html HTTP/1.1, con Host), la inoltra, riceve la risposta e la gira al client. Se ha in cache una copia fresca risponde da solo. Il codice di un proxy semplice è in Esercizio - Proxy HTTP con filtro degli host (sul modello della prova pratica). Cosa deve fare un proxy corretto:

  • Togliere gli header hop-by-hop. Alcuni header riguardano solo la connessione fra due nodi adiacenti e non devono attraversare il proxy: Connection, Keep-Alive, Proxy-Connection (non standard), Proxy-Authenticate, Proxy-Authorization, TE, Trailer, Transfer-Encoding, Upgrade, più ogni header elencato dentro Connection. Gli altri sono end-to-end e passano. Il proxy gestisce con client e server due connessioni separate, con la propria persistenza.
  • Via. Aggiunge Via: 1.1 nome-proxy (versione del protocollo e nome del nodo) a richieste e risposte: documenta il percorso e permette di rilevare i cicli (un proxy che si ritrova nel Via).
  • Indirizzo del client. Il server vede solo l'IP del proxy. L'header X-Forwarded-For: 192.0.2.7 (o il Forwarded standard, RFC 7239) comunica l'indirizzo del client; è un'informazione dichiarata dal proxy, non verificabile.
  • Errori propri. 502 Bad Gateway se la risposta del server è non valida o la connessione non riesce; 504 Gateway Timeout se il server non risponde in tempo; 407 Proxy Authentication Required se il proxy richiede autenticazione (Proxy-Authenticate, Proxy-Authorization).
  • Cache. Un proxy è una cache condivisa: rispetta Cache-Control (private esclude, s-maxage vale per lui), Vary e le richieste condizionali; non memorizza le risposte a richieste con Authorization se non sono public.
  • Limiti e sicurezza. Corpi e header con limiti di dimensione; un proxy aperto a tutti (open proxy) viene sfruttato per nascondere attacchi: serve autenticazione o restrizione agli indirizzi della rete.

Per i client: l'impostazione si fa nel browser, con le variabili http_proxy/https_proxy, oppure con curl -x http://proxy:8888 URL. Un intercepting proxy invece funziona senza configurazione (il firewall reindirizza la porta 80), ma non può farlo per HTTPS senza intercettare TLS con un proprio certificato.

Il vantaggio della cache condivisa si misura con il tempo medio di accesso: con probabilità di hit h, tempo di hit T_hit e di miss T_miss, T_medio = h T_hit + (1 - h) T_miss (Livello applicazione - HTTPIl livello applicazione è il più alto della pila: offre servizi all'utente con una connessione logica tra le due applicazioni e riceve servizi solo dal trasporto (DNS, HTTP, e-mail, FTP). Il Web (WWW, nato al CERN nel 1989) è un servizio client-server distribuito di pagine collegate da ipertesti; ogni pagina ha un URL protocollo://host:porta/percorso. HTTP: il client manda una richiesta, il server una risposta, su TCP (server sulla porta 80, client su una porta temporanea); senza stato. Messaggi di testo (riga di richiesta o di stato, intestazioni, riga vuota, corpo), metodi GET, POST, HEAD, PUT, DELETE, codici di stato 2xx-5xx. Una pagina con N oggetti incorporati richiede 2(N+1) RTT con connessioni non persistenti e (N+2) RTT con connessione persistente (trascurando la trasmissione). I cookie danno memoria al protocollo: Set-Cookie nella risposta, Cookie nelle richieste, file nel browser e base di dati nel sito. Un proxy (web cache) tiene le copie delle risposte recenti: meno carico sul server, meno traffico, meno ritardo.Livello applicazione - HTTP →).

Caching distribuito

Una sola cache ha capacità e banda limitate. Due soluzioni per scalare:

Gerarchie e cache cooperanti. Le cache si organizzano ad albero (cache dell'ufficio, cache del provider, cache nazionale: parent) e a pari livello (sibling). Un miss locale viene prima chiesto al padre o ai fratelli e solo poi al server. I fratelli si interrogano con protocolli leggeri su UDP come ICP (Internet Cache Protocol, RFC 2186: la risposta è HIT o MISS) o HTCP, oppure si scambiano riassunti del contenuto (cache digest).

Esempio. Hit locale 40% con 10 ms; dei miss la metà trova l'oggetto nella cache padre (40 ms), il resto va al server (200 ms). Tempo medio: 0,4 · 10 + 0,6 · (0,5 · 40 + 0,5 · 200) = 4 + 72 = 76 ms, contro 0,4 · 10 + 0,6 · 200 = 124 ms senza il livello intermedio.

Partizionare lo spazio degli URL. Con N cache in parallelo si assegna ogni URL a una cache con una funzione hash: cache = hash(URL) mod N. Così lo stesso oggetto non è duplicato e il client sa a quale cache rivolgersi. Se però N cambia (una cache si rompe o se ne aggiunge una), con il resto della divisione quasi tutti gli URL cambiano cache: passando da 4 a 5 cache, un URL resta nella stessa cache solo se k mod 4 = k mod 5, cioè in 4 casi su 20, e l'80% dei contenuti va rimesso in cache. La soluzione è l'hashing coerente (consistent hashing, molto usato nei sistemi distribuiti; il protocollo CARP delle cache proxy ottiene lo stesso effetto con una variante, highest random weight): cache e URL sono posti su uno stesso anello di valori hash e ogni URL va alla prima cache che segue sull'anello; aggiungendo o togliendo una cache si spostano solo gli URL del tratto adiacente, in media 1/N del totale (20% con 5 cache).

CDN. Una content delivery network è una rete di cache (edge server) distribuite nel mondo: il sito delega i contenuti, e il DNS del sito risponde con l'indirizzo dell'edge più vicino al client (l'host www.sito.it è un CNAME verso il nome della CDN, che a sua volta risponde in base alla posizione del resolver). La latenza scende perché il contenuto è vicino e il server di origine è alleggerito.

HTTP CONNECT e i tunnel

Un proxy che legge le richieste HTTP non può farlo con HTTPS: il traffico è cifrato con TLS fra browser e server (Crittografia asimmetrica, RSA e TLSCrittografia asimmetrica (a chiave pubblica): chiave di cifratura pubblica v_B, chiave di decifratura privata s_B, matematicamente legate; C = E_vB(P), P = D_sB(C); dà riservatezza. I certificati digitali, emessi da una Certificate Authority e firmati con la sua chiave privata, legano un'identità a una chiave pubblica. Firma digitale: hash del messaggio cifrato con la chiave privata; garantisce autenticità, integrità e non ripudio. RSA: si scelgono due primi grandi p e q, N = pq, phi(N) = (p-1)(q-1), e coprimo con phi(N), d = e^(-1) mod phi(N); chiave pubblica (N, e), segreta (N, d); cifratura c = m^e mod N, decifratura m = c^d mod N con m < N (teorema di Eulero); sicura finché la fattorizzazione è difficile (N di almeno 2048 bit); il padding casuale (PKCS#1 v1.5: 00 02 [casuale] 00 [m]) difende da malleabilità e determinismo. La firma RSA è s = m^d mod N, verificata con s^e mod N. TLS (su TCP) autentica gli estremi con il certificato, cifra i dati con una chiave di sessione simmetrica e garantisce l'integrità con i MAC; da TLS 1.0 a 1.3 la chiave si ricava con Diffie-Hellman e l'handshake si accorcia. DTLS è la versione per UDP.Crittografia asimmetrica, RSA e TLS →). Il client chiede allora al proxy di fare da semplice tunnel: il metodo CONNECT (RFC 9110 par. 9.3.6).

Definizione (CONNECT). CONNECT host:porta HTTP/1.1: chiede al proxy di aprire una connessione TCP verso host:porta e, se ci riesce, di inoltrare senza interpretarli i byte in entrambe le direzioni finché una delle due parti chiude. Il request-target è in authority-form: solo host e porta, e la porta è obbligatoria.

http
CONNECT www.example.com:443 HTTP/1.1
Host: www.example.com:443
Proxy-Authorization: Basic bWFyaW86c2VncmV0bw==

Se il proxy riesce a collegarsi risponde con un qualunque 2xx:

http
HTTP/1.1 200 Connection established

e da quel momento la connessione non è più HTTP: non ci sono Content-Length né Transfer-Encoding (il server non deve mandarli e il client deve ignorarli se li riceve) e quello che il client scrive è il TLS del server di origine.

 browser                 proxy                          server (www.example.com:443)
   |-- CONNECT www.example.com:443 -->|
   |                                  |------ connessione TCP ------>|
   |<-- 200 Connection established ---|
   |================ tunnel: byte inoltrati in entrambe le direzioni ==============|
   |-- ClientHello (TLS) ------------>|----------------------------->|
   |<-- ServerHello, certificato -----|<-----------------------------|
   |== richiesta HTTP cifrata ========|==============================>|

Il proxy vede solo a chi ci si collega (host e porta) e quanti byte passano, non il contenuto, a meno che intercetti TLS presentandosi con un proprio certificato (e allora il browser deve fidarsi della sua CA). Gli errori prima del tunnel sono normali risposte HTTP: 403 Forbidden (vietato), 407 (serve autenticazione), 502 Bad Gateway, 504 Gateway Timeout.

Il cuore del programma è un ciclo che aspetta dati da uno dei due socket con poll e li gira all'altro (System call POSIX, file descriptor e API delle socketLe system call sono le funzioni con cui un programma in user space chiede servizi al kernel (open, read, write, close, fork, pipe, dup2, socket, bind, listen, accept, connect); restituiscono -1 e impostano errno in caso di errore; un file descriptor e' un intero che indicizza la tabella dei file aperti del processo (0 stdin, 1 stdout, 2 stderr) e vale per file, pipe e socket; read e write possono trasferire MENO byte del richiesto (e sui socket TCP non c'e' nessun confine fra i messaggi), quindi servono cicli write_all e read_exact; l'API delle socket crea un punto finale di comunicazione (socket), lo lega a indirizzo e porta (bind), lo rende passivo (listen), accetta connessioni (accept) o si connette (connect); getaddrinfo traduce nomi e porte in indirizzi (IPv4 e IPv6); UDP usa sendto e recvfrom e conserva i confini dei datagrammi, TCP e' uno stream affidabile.System call POSIX, file descriptor e API delle socket →):

c
struct pollfd pf[2] = { {.fd = client, .events = POLLIN}, {.fd = server, .events = POLLIN} };
for (;;) {
    poll(pf, 2, 60000);                                   /* 60 s di silenzio: si chiude */
    for (int i = 0; i < 2; i++)
        if (pf[i].revents & (POLLIN | POLLHUP | POLLERR)) {
            ssize_t r = read(pf[i].fd, buf, sizeof buf);
            if (r <= 0) { shutdown(pf[1 - i].fd, SHUT_WR); /* EOF: half-close dell'altro lato */ }
            else write_all(pf[1 - i].fd, buf, r);
        }
}

Quando un lato smette di scrivere (EOF) si fa shutdown(altro, SHUT_WR) per propagare la chiusura, ma si continua a trasferire nell'altra direzione finché anche l'altro lato ha finito (half-close). Un tunnel dura a lungo, quindi il proxy usa un processo (o un thread, o un ciclo di eventi) per tunnel (Client e server TCP in C - bind, listen, accept e processi concorrentiUn client TCP in C fa getaddrinfo, socket, connect, poi write e read; un server fa socket, setsockopt(SO_REUSEADDR), bind, listen e un ciclo di accept. Un server iterativo serve un client alla volta e un client lento blocca tutti; un server concorrente con fork crea un processo figlio per ogni connessione: il figlio chiude il socket di ascolto e serve la connessione, il padre chiude il socket di connessione e torna ad accept. I figli terminati diventano zombie finche' il padre non li raccoglie con waitpid, di solito in un gestore di SIGCHLD con WNOHANG in un ciclo; i descrittori non chiusi tengono viva la connessione (nessun FIN) o esauriscono EMFILE; uno stato CLOSE_WAIT che si accumula indica una close mancante. Alternative: thread, pre-fork, I/O multiplexing con poll o epoll.Client e server TCP in C - bind, listen, accept e processi concorrenti →).

Sicurezza. CONNECT verso porte qualsiasi trasforma il proxy in un ponte verso qualunque servizio (posta, SSH, servizi interni): un attaccante potrebbe usare CONNECT mail.example.com:25 per inviare spam o CONNECT 10.0.0.5:22 per raggiungere macchine della rete interna (SSRF). Un proxy deve limitare le porte (di solito solo 443, a volte 80 e 8443), eventualmente gli host, richiedere autenticazione e registrare i collegamenti (Esercizio - Proxy con tunnel CONNECT (sul modello della prova pratica)). HTTP/2 e HTTP/3 definiscono un CONNECT analogo che apre un tunnel su un singolo flusso.

Gateway applicativi

Un gateway applicativo (application-layer gateway) è un intermediario che opera al livello applicazione e fa da ponte fra due sistemi: per il client è il server, per il sistema a valle è un client. Il caso più importante oggi è il reverse proxy, che sta davanti a uno o più server:

   Internet                reverse proxy                      rete interna
 client ---- HTTPS ----> [ TLS, cache, filtri ] ---- HTTP ----> server applicativi 1, 2, 3

Funzioni tipiche:

Un reverse proxy si comporta come un forward proxy per quanto riguarda gli header (toglie gli hop-by-hop, aggiunge Via e X-Forwarded-For/X-Forwarded-Proto, sceglie Host) e usa gli stessi errori (502, 503, 504). Lo stesso schema con altri protocolli: un gateway di posta (SMTP verso un altro sistema), un ALG (application-level gateway) in un firewall o in un NAT, che deve riscrivere gli indirizzi dentro il payload di protocolli come FTP (NAT e indirizzi privatiUna rete privata (intranet) usa il protocollo TCP/IP con indirizzi privati ($10.0.0.0/8$, $172.16.0.0/12$, $192.168.0.0/16$), riutilizzabili da intranet diverse ma da non instradare in Internet: i router di bordo scartano i pacchetti con indirizzi privati. Per accedere a Internet servono un proxy applicativo (uno per applicazione) o il NAT (Network Address Translation): un router che traduce indirizzi privati in indirizzi pubblici di un pool, con una tabella NAT e un'associazione dinamica per sessione. Il NAT tradizionale è outbound: Basic NAT traduce solo l'IP (uno a uno, quindi servono tanti indirizzi pubblici quante le sessioni contemporanee), NAPT traduce anche la porta ([IP privato, porta] $\to$ [IP pubblico, porta del NAT]) e permette a molte sessioni di condividere un solo IP pubblico. Il Twice NAT permette sessioni anche dall'esterno, con un DNS interno e associazioni statiche.NAT e indirizzi privati →, FirewallUn firewall è un componente (hardware, software o entrambi) che blocca il traffico non autorizzato da una rete verso un'altra: filtra i dati, reindirizza il traffico, protegge dagli attacchi. Requisiti: tutto il traffico tra due zone di fiducia deve passare dal firewall, passa solo il traffico autorizzato, il firewall stesso deve essere immune alle intrusioni. L'amministratore configura le politiche (controllo di utente, di servizio, di direzione); un pacchetto può essere accettato, negato o rifiutato (negato più messaggio ICMP alla sorgente). Il firewall decide con le informazioni di intestazione dei livelli 2-4 (indirizzi, protocollo, porte, flag SYN/ACK/FIN). Packet filter: senza stato, filtra in ingresso e in uscita con regole su indirizzi, protocollo, porte, flag (esempi SMTP, Telnet, X11 e correzioni con porte sorgente e flag ACK). Stateful firewall: tiene traccia delle connessioni (NEW, ESTABLISHED, RELATED, INVALID), limita connessioni al secondo, timeout. Firewall applicativo/proxy: ispeziona il livello applicazione, apre una connessione separata verso la destinazione; sicuro ma lento. Linux: netfilter con 5 hook IPv4 e 5 verdetti, iptables (tabelle e catene), connection tracking nf_conntrack.Firewall →).

intermediario livello chi lo sceglie cosa fa
NAT, router rete la rete inoltra o traduce gli indirizzi dei pacchetti
tunnel (CONNECT) trasporto il client inoltra i byte senza capirli
forward proxy applicazione il client capisce HTTP, filtra, mette in cache
reverse proxy / gateway applicazione il server capisce HTTP, bilancia, traduce, protegge

Esercizi collegati

Errori tipici

  • Dimenticare di convertire QDCOUNT, ANCOUNT, ID da e verso big endian; leggere un nome senza seguire i puntatori di compressione, o seguirli senza limite di salti.
  • Scrivere nel messaggio DNS la lunghezza in decimale o in testo (3www7example3com0): sono byte, non caratteri 3 e 7.
  • Credere che il DNS usi solo UDP: le risposte troncate e i trasferimenti di zona usano TCP.
  • Inoltrare nel proxy gli header hop-by-hop (Connection, Proxy-Connection, Transfer-Encoding) e lasciare l'URI assoluto verso il server di origine.
  • Dimenticare che il server di origine vede l'IP del proxy: serve X-Forwarded-For.
  • Nel tunnel CONNECT: aspettarsi Content-Length nella risposta 200; chiudere i due socket all'EOF di un lato perdendo i dati dell'altro; accettare qualsiasi porta di destinazione.
  • Usare CONNECT come se il proxy cifrasse: il proxy inoltra byte, il TLS è fra client e server.
  • Confondere forward proxy (scelto dal client) e reverse proxy (scelto dal server).

Domande d'esame

1. Descrivere la struttura di un messaggio DNS e dire come si codifica www.example.com nella domanda; cos'è la compressione dei nomi? Traccia. Header di 12 byte (ID, flag con QR, RD, RA, TC e RCODE, quattro contatori), poi Question, Answer, Authority, Additional. www.example.com = 03 www 07 example 03 com 00 (17 byte), seguito da QTYPE e QCLASS da 2 byte. Compressione: un nome già comparso si sostituisce con un puntatore a 14 bit (byte che inizia con 11, es. c0 0c = offset 12) all'offset dove il nome comincia; il lettore segue i puntatori con un limite di salti.

2. Un browser deve raggiungere https://www.example.com passando per un proxy. Descrivere i messaggi e cosa vede il proxy. Traccia. Il browser invia CONNECT www.example.com:443 HTTP/1.1 (authority-form, porta obbligatoria); il proxy apre la connessione TCP verso il server e risponde 200 Connection established (senza Content-Length); da lì inoltra i byte in entrambe le direzioni; il browser esegue l'handshake TLS e la richiesta HTTP cifrata con il server. Il proxy vede host e porta e il volume dei dati, non il contenuto (salvo interception TLS). Deve limitare le porte e richiedere autenticazione.

3. Un forward proxy riceve GET http://www.example.com/a.html HTTP/1.1 con Connection: keep-alive e Proxy-Connection: keep-alive. Cosa invia al server? Traccia. Apre la connessione verso www.example.com:80; invia GET /a.html HTTP/1.1 con Host: www.example.com; non inoltra Connection né Proxy-Connection (hop-by-hop; imposta il proprio Connection); aggiunge Via: 1.1 proxy e X-Forwarded-For: <IP client>; poi gira la risposta al client, con i propri Via e le proprie regole di persistenza.

Versione ripasso

c
static int dns_read_name(const uint8_t *m, size_t len, size_t *pos, char *out, size_t outsz)
{
    size_t p = *pos, o = 0;
    int jumped = 0, hops = 0;
    for (;;) {
        if (p >= len)
            return -1;
        uint8_t l = m[p];
        if (l == 0) {
            p++;
            break;
        }
        if ((l & 0xC0) == 0xC0) {
            if (p + 1 >= len)
                return -1;
            size_t off = (size_t)(l & 0x3F) << 8 | m[p + 1];
            if (!jumped)
                *pos = p + 2;
            jumped = 1;
            if (off >= len || ++hops > 16)
                return -1;
            p = off;
            continue;
        }
        if (l & 0xC0)
            return -1;
        p++;
        if (p + l > len || o + l + 2 > outsz)
            return -1;
        if (o > 0)
            out[o++] = '.';
        memcpy(out + o, m + p, l);
        o += l;
        p += l;
    }
    out[o] = '\0';
    if (o == 0)
        snprintf(out, outsz, ".");
    if (!jumped)
        *pos = p;
    return 0;
}
c
static int is_hop_by_hop(const char *name)
{
    static const char *hop[] = {"Connection", "Proxy-Connection", "Keep-Alive", "Proxy-Authenticate",
                                "Proxy-Authorization", "TE", "Trailer", "Transfer-Encoding", "Upgrade"};
    for (size_t i = 0; i < sizeof hop / sizeof hop[0]; i++)
        if (strcasecmp(name, hop[i]) == 0)
            return 1;
    return 0;
}
  • Errori tipici: interi DNS non convertiti da/in big endian; lunghezze delle etichette scritte come testo; puntatori di compressione ignorati; hop-by-hop inoltrati; URI assoluto verso l'origine; Content-Length atteso nel 200 di CONNECT; chiusura di entrambi i lati all'EOF di uno; porte di CONNECT non limitate; forward e reverse proxy confusi.

Esercizi su questo argomento

Teoria collegata