Skip to main content

Module 3 · Networking

Network Diagnostics

  • Lesson 9/15
  • 35 min
  • Difficulty: Intermediate

This lesson is not available in English yet: you are reading it in Italian.

Objective

Dividere una connessione che non funziona in quattro anelli e stabilire con un comando quale anello e' rotto, invece di provare rimedi a caso

Prerequisites

Prima di leggere: ricostruisci una spiegazione

Nella lezione sul firewall hai misurato una regola che accettava il traffico sulla porta 8080, contava i pacchetti, e non faceva funzionare la connessione. La spiegazione era che i pacchetti di ritorno arrivano con una porta di destinazione diversa. Ricostruiscila senza rileggere: quale porta hanno, chi la sceglie, e perché cambia a ogni connessione?

In questa lezione quella spiegazione non resta una spiegazione: la vedrai, pacchetto per pacchetto, con i numeri di porta sullo schermo. È il senso di una lezione sulla diagnostica. Gli strumenti non servono a sapere più comandi, servono a rendere visibile ciò che finora hai dovuto dedurre.

Il problema: «non funziona» non è una diagnosi

Ti arriva una segnalazione: il servizio non risponde. Potresti provare quindici cose, e in genere è quello che si fa: si riavvia il servizio, si guarda il firewall, si pinga qualcosa, si riavvia la macchina. A volte funziona, e quando funziona non sai perché, quindi la volta dopo ricominci da zero.

L'abilità che allenerai qui è un'altra: dividere la catena in anelli e provare un anello alla volta, scegliendo ogni volta lo strumento che risponde a quella domanda e nient'altro. Si chiama pratica deliberata perché ogni passaggio ha un obiettivo singolo e un modo di dire se è riuscito, e perché migliorare significa correggere una debolezza precisa e non «fare più esperienza».

Gli anelli di una connessione sono quattro: il nome diventa un indirizzo, l'indirizzo è raggiungibile, la porta è aperta, il servizio risponde correttamente. Quattro domande, quattro strumenti.

Primo anello: il nome. E due strumenti che danno risposte diverse

La domanda è «questo nome a quale indirizzo corrisponde», e lo strumento ovvio è dig. Lo strumento giusto però dipende da chi fa la domanda nella realtà, ed è qui che si annida l'errore più comune della diagnostica DNS. Guarda queste due risposte, raccolte sulla stessa macchina a pochi secondi di distanza:

$ dig +noall +answer example.com A
example.com.		70	IN	A	172.66.147.243
example.com.		70	IN	A	104.20.23.154

$ getent hosts example.com
2606:4700:10::ac42:93f3 example.com
2606:4700:10::6814:179a example.com

Due comandi, due famiglie di indirizzi completamente diverse. Non è un errore di nessuno dei due: dig interroga il DNS e gli ho chiesto i record A, cioè IPv4; getent usa la libreria di risoluzione del sistema, la stessa che usano i programmi, e quella ha restituito gli indirizzi IPv6 perché li preferisce. Il tuo programma si collegherà a un indirizzo IPv6, e tu stavi diagnosticando un indirizzo IPv4.

La regola che ne segue: dig per capire che cosa dice il DNS, getent hosts per capire che cosa farà il programma. Se le due risposte divergono, la divergenza è il problema, e di solito la causa sta in /etc/hosts o nella preferenza fra le due famiglie di indirizzi.

Un terzo dato serve a sapere chi sta rispondendo. Su questa macchina /etc/resolv.conf contiene nameserver 127.0.0.53, che non è un server DNS vero ma un intermediario locale, e resolvectl status lo conferma con resolv.conf mode: stub. Significa che le domande passano da un servizio locale prima di uscire, e che una cache intermedia può spiegare una risposta vecchia.

Secondo e terzo anello: la strada e la porta

Per la strada hai già lo strumento: ip route get della lezione sulle interfacce, che dice se una destinazione è raggiungibile e da dove. Per la porta lo strumento è ss, e il suo valore sta nei filtri, perché su una macchina reale l'elenco completo è illeggibile.

# ss -ltn "sport = :8080"
State Recv-Q Send-Q Local Address:Port Peer Address:Port

# ss -ltnp "sport = :8080"
State  Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0      5          127.0.0.1:8080      0.0.0.0:*    users:(("python3",pid=1350567,fd=3))

Prima e dopo l'avvio del servizio. Le lettere valgono la pena: -l solo quelli in ascolto, -t solo TCP, -n numeri invece di nomi di servizio, -p il processo. L'ultima colonna è la più utile di tutte, perché nomina il programma e il suo identificativo: risponde a «chi tiene occupata questa porta», che è la domanda che si fa davvero.

E c'è un dettaglio nella colonna dell'indirizzo che spiega metà delle segnalazioni di servizi irraggiungibili: 127.0.0.1:8080 significa che il servizio ascolta solo sull'interfaccia di anello, quindi nessuno da fuori lo raggiungerà, qualunque cosa dica il firewall. Se la colonna dicesse 0.0.0.0:8080 ascolterebbe su tutte le interfacce. È una riga di configurazione del servizio, non un problema di rete, e distinguere le due cose evita di passare un'ora sul firewall.

Gli strumenti che potresti non avere

Due strumenti che ogni guida alla diagnostica di rete nomina non sono installati su questa macchina, e vale la pena dirlo invece di dare per scontato che ci siano. Misurato il 2026-10-30: command -v traceroute non restituisce nulla, e apt-cache policy traceroute dichiara Installato: (nessuno) con candidato 1:2.1.6-1build1. Lo stesso vale per nmap.

Al loro posto ci sono due alternative presenti di serie. tracepath fa un lavoro analogo a traceroute e non richiede privilegi, e misura anche la dimensione massima dei pacchetti lungo il percorso. mtr combina il tracciamento del percorso con una misurazione continua, e serve quando il problema è intermittente e un singolo tentativo non lo cattura. La lezione da portarsi via non è quale dei due usare, è che la prima cosa da fare su una macchina nuova è verificare quali strumenti ci sono: una diagnosi che comincia con l'installazione di un pacchetto su un server in avaria è una diagnosi che comincia male.

Esempio svolto: tre guasti, tre codici di uscita

Il problema: hai un indirizzo e una porta e la connessione non riesce. Vuoi sapere a quale anello è rotta la catena, senza accedere al server e senza chiedere a nessuno.

L'informazione disponibile è che curl non lascia un codice di uscita generico: ne lascia uno diverso per famiglia di guasto. Il ragionamento: se i codici sono diversi, allora il codice è una diagnosi, e provare lo stesso comando contro tre guasti costruiti su misura mi dà una tabella di riferimento che userò ogni volta.

$ curl -sS --max-time 3 http://questo-nome-non-esiste.invalid/
curl: (6) Could not resolve host: questo-nome-non-esiste.invalid

$ curl -sS --max-time 3 http://127.0.0.1:8081/
curl: (7) Failed to connect to 127.0.0.1 port 8081 after 0 ms: Could not connect to server

$ curl -s -o /dev/null --max-time 3 http://127.0.0.1:8080/   # con una regola di drop attiva
  tempo: 3.00 s
  exit di curl=28

Codice 6: il primo anello è rotto, è un problema di nome, e il firewall non c'entra niente. Codice 7 immediato: la strada c'è, la porta risponde con un rifiuto, quindi non c'è nessuno in ascolto oppure c'è un reject. Codice 28 con l'attesa che arriva fino al limite: qualcuno sta buttando i pacchetti in silenzio, e questo è il firewall.

La decisione che prendo: davanti a una segnalazione di servizio irraggiungibile, il primo comando è curl con un limite di tempo breve, e il codice di uscita decide dove guardare. L'alternativa che ho scartato è cominciare dal ping, per due motivi misurati. Il primo è che ping usa un protocollo diverso da quello del tuo servizio, e tantissime reti lo bloccano senza bloccare il resto: un ping che non risponde non dice quasi nulla. Il secondo è che nella lezione sulle interfacce ho visto un ping riuscire verso un indirizzo locale senza attraversare nessun cavo.

La verifica che chiude il ragionamento è il quarto anello, e si fa guardando i pacchetti. Questo è lo scambio completo di una singola richiesta HTTP, catturato nel laboratorio isolato:

# tcpdump --no-promiscuous-mode -Z root -i lo -n -c 8 "tcp port 8080"
IP 127.0.0.1.54674 > 127.0.0.1.8080: Flags [S], seq 1849229325, length 0
IP 127.0.0.1.8080 > 127.0.0.1.54674: Flags [S.], seq 2504559539, ack 1849229326, length 0
IP 127.0.0.1.54674 > 127.0.0.1.8080: Flags [.], ack 1, length 0
IP 127.0.0.1.54674 > 127.0.0.1.8080: Flags [P.], seq 1:79, ack 1, length 78: HTTP: GET / HTTP/1.1
IP 127.0.0.1.8080 > 127.0.0.1.54674: Flags [.], ack 79, length 0
IP 127.0.0.1.8080 > 127.0.0.1.54674: Flags [P.], seq 1:157, ack 79, length 156: HTTP: HTTP/1.0 200 OK

Guarda i numeri di porta e ritrova la spiegazione del firewall. Il client parte dalla porta 54674, scelta dal kernel al momento, verso la 8080. Le risposte del server vanno dalla 8080 alla 54674. Una regola che autorizza la porta di destinazione 8080 copre tre di questi sei pacchetti e non gli altri tre: non è una teoria, sono le righe qui sopra.

Le tre lettere fra parentesi quadre sono il dialogo di apertura. [S] è la richiesta di connessione, [S.] è la conferma, il punto da solo è la conferma della conferma, e [P.] porta dati. Se vedi partire [S] e non arrivare mai [S.], il problema è prima del servizio. Se vedi [R], qualcuno ha rifiutato attivamente. L'errore che avrei potuto commettere è lanciare tcpdump senza un filtro: su una macchina reale l'uscita scorre troppo in fretta per essere letta, e la diagnosi si perde nel rumore.

Esercizio 1, guidato: costruisci i tre guasti

Nel laboratorio isolato, costruisci deliberatamente i tre guasti dell'esempio e raccogli per ciascuno il codice di uscita di curl e il tempo trascorso. Il primo guasto ti serve una precisazione: dentro il namespace non c'è risoluzione dei nomi, quindi il codice 6 lo ottieni comunque, ma per una ragione diversa. Scrivi quale, e come lo verifichi. Criterio: hai una tabella di tre righe con guasto, codice e tempo, e per ogni riga il comando che distingue quel guasto dagli altri due.

Esercizio 2, trasferimento: la stessa diagnosi su un servizio diverso

Prendi un servizio di tipo diverso, non HTTP: un database, un server di posta, una cache. Non puoi usare curl come prima. Riprogetta i quattro anelli per quel servizio: quale strumento risponde a ciascuna delle quattro domande, e che differenza fa il fatto che il protocollo non sia HTTP? Criterio di successo: hai quattro comandi, uno per anello, e per almeno uno di essi sai dire perché lo strumento HTTP non sarebbe servito.

Esercizio 3, autonomo: la porta occupata

Sulla tua macchina, trova un servizio che ascolta su una porta e stabilisci tre cose con un solo comando: quale programma è, su quali interfacce ascolta, e se sarebbe raggiungibile da un'altra macchina della rete. Criterio: la risposta alla terza domanda viene da una colonna dell'output, non da una prova di connessione.

Soluzioni commentate

Esercizio 1, la precisazione sul primo guasto. Dentro il namespace il codice 6 arriva perché non esiste una strada verso il server DNS, non perché il nome non esista. Sono due guasti diversi che producono lo stesso codice, e distinguerli è esattamente il tipo di raffinamento che la diagnostica richiede. Il comando che li separa è dig: con un nome inesistente e un DNS raggiungibile, dig risponde con stato NXDOMAIN, che è una risposta; con un DNS irraggiungibile, dig non risponde affatto e scade. Quindi la riga della tua tabella per il codice 6 va divisa in due, e il comando della colonna «come lo distinguo» è dig.

Sugli altri due, l'errore più comune è leggere il codice e non il tempo. Il codice 7 può arrivare subito oppure dopo un'attesa, e la differenza conta: subito significa rifiuto attivo, dopo un'attesa significa che il tentativo di connessione è andato a vuoto. Per questo nella tabella la colonna del tempo non è decorativa, e per questo nell'esempio ho messo --max-time su ogni invocazione: senza un limite dichiarato, il tempo non è un dato confrontabile.

Esercizio 2. I primi tre anelli non cambiano, e questa è la risposta più importante: getent hosts, ip route get e ss funzionano identici per qualunque servizio, perché stanno sotto il protocollo applicativo. Cambia solo il quarto anello, e cambia in un modo che vale la pena capire: curl parla HTTP, quindi su un database non può dirti se il servizio risponde correttamente, al massimo ti dice che la porta accetta connessioni.

Per il quarto anello hai due strade. La prima è il client nativo del servizio, che è la risposta giusta quando esiste e quando hai le credenziali. La seconda, utile quando non hai né l'uno né le altre, è ss più tcpdump: se vedi la connessione stabilita in ss ma nessun dato scambiato in tcpdump, il servizio accetta e non parla, che è un sintomo diverso da «la porta è chiusa». Perché telnet sulla porta non basta più come una volta: apre la connessione, quindi prova l'anello tre, e per i protocolli che aspettano che sia il server a parlare primo ti dà un'impressione di successo senza aver verificato niente.

Esercizio 3. Il comando è ss -ltnp, e le tre risposte stanno tutte nella sua uscita: la colonna Process dà il programma, la colonna Local Address:Port dà l'interfaccia. Sulla terza domanda, il criterio è quello che hai visto nella spiegazione: se l'indirizzo locale è 127.0.0.1, il servizio non è raggiungibile da fuori e nessuna regola di firewall lo renderebbe tale; se è 0.0.0.0 o un indirizzo di un'interfaccia vera, lo è, e il firewall diventa l'unica cosa che lo protegge.

Perché la prova di connessione sarebbe una risposta più debole. Provare da un'altra macchina risponde «sì» o «no» e non dice perché: un «no» può venire dall'indirizzo di ascolto, dal firewall locale, da un firewall di rete in mezzo, o dal fatto che il servizio era appena riavviato. La colonna di ss risponde a una sola di quelle cause e la risponde con certezza, e in diagnostica una risposta certa su un anello vale più di una risposta incerta su tutta la catena.

Che cosa adesso sai fare

Sai dividere una connessione in quattro anelli e provarne uno alla volta, con lo strumento che risponde a quella domanda: getent hosts e dig per il nome, ip route get per la strada, ss -ltnp per la porta e per il processo che la tiene, tcpdump con un filtro per lo scambio. Sai leggere il codice di uscita di curl come una diagnosi, insieme al tempo trascorso.

Sai che dig e getent possono dare risposte diverse sullo stesso nome, e sai che quando divergono la divergenza è il problema. E hai visto, nei numeri di porta di una cattura vera, perché una regola di firewall sulla porta di destinazione copre metà di una connessione. La verifica concreta: esegui ss -ltnp sulla tua macchina e trova un servizio che ascolta su tutte le interfacce senza che tu lo volessi.