Aller au contenu
Diagnostiquer un problème réseau

Diagnostiquer un problème réseau

200 Pratiquer ⏱ 1 h 30 reseaulinuxtcptcpdump

À la fin, vous saurez

  • Conduire un diagnostic réseau par hypothèses, couche par couche ou par dichotomie, en ne changeant qu'une chose à la fois
  • Associer chaque message d'erreur courant (refused, timed out, no route, unreachable, name not known) à sa cause probable
  • Vérifier route, voisin, écoute et connexion avec ip, ss, nc et curl, et mesurer les phases d'une requête
  • Capturer le trafic utile avec tcpdump et un filtre précis, et lire une ligne de capture TCP
  • Résoudre les pannes classiques : mauvaise adresse d'écoute, filtrage, plage d'adresses, trou noir MTU, épuisement de ports
  • Capturer sans exposer de données personnelles ni de secrets

Prérequis

Testé avec curl 8.5.0 iproute2 6.1.0 mtr 0.95 netcat-openbsd 1.226 noyau 7.0 (HWE) tcpdump 4.99.4 ubuntu 24.04 , vérifié le 5 octobre 2026

Pourquoi

« Le réseau ne marche pas » est la phrase la moins informative de l'exploitation. Elle peut vouloir dire qu'un nom ne se résout pas, qu'une route manque, qu'un pare-feu jette des paquets, qu'un service écoute sur la mauvaise adresse, qu'un paquet trop gros disparaît en chemin, ou que l'application elle-même est saturée. Chacune de ces causes a des symptômes distincts, et des remèdes qui n'ont rien en commun.

Face à cette phrase, deux attitudes s'opposent. La première consiste à essayer : redémarrer le service, ouvrir un port « pour voir », désactiver le pare-feu, relancer la machine. Parfois cela marche, et l'on ne sait pas pourquoi ; souvent cela empire, parce que l'on a changé trois choses à la fois, dont une ouvre une brèche que personne ne refermera. La seconde consiste à interroger : formuler une hypothèse, la vérifier par une commande dont on sait lire la réponse, et avancer. Elle paraît plus lente ; elle est presque toujours plus rapide, et elle laisse une trace que l'on pourra relire au prochain incident.

Cette dernière leçon rassemble tout le cours en une méthode. Elle s'appuie sur les mécanismes vus jusqu'ici (adresses et routes, ARP, ICMP et MTU, NAT, ports, états de TCP) et sur les outils que l'on trouve sur n'importe quel serveur Linux. Elle se termine sur cinq pannes de Signalements, inspirées de situations que toute équipe d'exploitation rencontre.

Les concepts

Une méthode plutôt qu'une liste de commandes

Un diagnostic est une suite d'hypothèses que l'on confirme ou que l'on élimine. Trois règles le rendent efficace :

  1. Décrire précisément le symptôme. Qui n'arrive pas à joindre quoi, depuis où, par quel protocole et quel port, depuis quand, avec quel message exact ? « Le répartiteur marque sig-app-1 hors service depuis 14 h 10, sig-app-2 est sain, curl depuis sig-app-1 vers 127.0.0.1:8000/sante répond 200 » contient déjà la moitié de la réponse.
  2. Avancer dans un ordre. Deux ordres fonctionnent :
    • de bas en haut, en suivant les couches du cours : le lien, puis IP et la route, puis le port et TCP, puis l'application. Chaque étape suppose la précédente ;
    • par dichotomie : on coupe le chemin en deux (« est-ce que ça passe jusqu'au répartiteur ? ») et l'on recommence dans la moitié fautive. C'est la méthode la plus rapide sur un long chemin, à condition d'avoir des points d'observation au milieu.
  3. Ne changer qu'une chose à la fois, et noter ce que l'on change. Un changement qui ne corrige rien est annulé avant le suivant.

Les questions, dans l'ordre

Pour une connexion d'un client vers un service, les questions suivantes couvrent l'immense majorité des pannes. Chacune a sa commande :

QuestionCommandeLeçon
Le nom se résout-il, et vers la bonne adresse ?getent hosts nom, resolvectl query nomcours DNS en profondeur
Ai-je une route vers cette adresse, par quelle interface et quelle source ?ip route get adresse5
Le lien est-il actif, le prochain saut répond-il à ARP ?ip -br link, ip neigh2
Les paquets atteignent-ils la destination, et reviennent-ils ?ping, tracepath, mtr6
Le service écoute-t-il, sur le bon port et la bonne adresse ?ss -ltnp sur le serveur9, 10
Un filtrage s'interpose-t-il (groupe de sécurité, pare-feu, ACL) ?règles du fournisseur, nft list ruleset7
La connexion TCP s'établit-elle ?nc -vz, curl -v10
L'application répond-elle, et en combien de temps ?curl -w, journaux de l'applicationcours HTTP et les API web

Avec l'habitude, on commence souvent par la question du milieu (la connexion TCP s'établit-elle ?), parce que sa réponse coupe l'espace des causes en deux : si elle s'établit, tout ce qui est en dessous fonctionne.

Lire les messages d'erreur

Les messages que remontent les programmes viennent presque tous d'un code d'erreur du noyau (errno), renvoyé par connect(2) ou par la résolution de noms. Chacun dit qui a répondu, et donc où chercher :

MessageCodeD'où il vientCauses typiques
Connection refusedECONNREFUSEDLa destination a répondu au SYN par un RSTRien n'écoute sur ce port, ou l'application écoute sur une autre adresse ; plus rarement un pare-feu qui rejette par RST
Connection timed outETIMEDOUTPersonne n'a répondu au SYN, après les retransmissionsGroupe de sécurité ou pare-feu qui jette, machine éteinte, route absente au retour, file accept saturée
No route to hostEHOSTUNREACHUne erreur ICMP « hôte injoignable » est revenue, ou la machine elle-même n'a pas obtenu de réponse ARPDestination absente du réseau local, pare-feu qui rejette avec ICMP (le réglage par défaut de firewalld produit ce message)
Network is unreachableENETUNREACHLa pile locale n'a trouvé aucune routePas de route par défaut, adresse d'une famille non configurée (IPv6 sans route IPv6), interface désactivée
Name or service not knownEAI_NONAMELa résolution de noms a échoué : aucune connexion n'a même été tentéeNom mal écrit, enregistrement DNS absent, mauvais résolveur
Temporary failure in name resolutionEAI_AGAINLe résolveur n'a pas pu répondreServeur DNS injoignable, souvent un problème de réseau sortant

Deux remarques importantes. D'abord, un délai dépassé ne prouve pas que le SYN s'est perdu : la réponse a pu se perdre au retour, par exemple quand la route de retour passe par un autre chemin que l'aller, ou qu'un filtrage à état ne voit qu'un sens. Ensuite, beaucoup de programmes reformulent ces messages : curl affiche « Couldn't connect to server » avec un code de sortie 7 pour un refus comme pour une absence de route, et le code 28 pour un délai. Quand le message n'est pas clair, nc -v affiche le texte exact du noyau.

Les outils et ce qu'ils regardent

  • ip (paquet iproute2) montre l'état de la machine : interfaces (ip -br link, ip -br addr), routes (ip route, et surtout ip route get, qui donne la décision de routage pour une adresse précise), voisins ARP et NDP (ip neigh).
  • ss montre les sockets : celles qui écoutent (-l), les connexions et leurs états (-a), les processus propriétaires (-p), les informations internes de TCP (-i, leçon 11).
  • nc (netcat) ouvre une connexion TCP ou UDP brute : nc -vz hôte port teste l'établissement sans rien envoyer.
  • curl fait une vraie requête HTTP, et sait mesurer chacune de ses phases.
  • ping, tracepath, traceroute et mtr testent le chemin avec ICMP ou UDP (leçon 6). mtr combine traceroute et ping en continu, et affiche les pertes et les temps à chaque saut.
  • tcpdump capture les paquets eux-mêmes : c'est l'arbitre final, quand les autres outils se contredisent.

Capturer avec tcpdump

tcpdump écoute une interface et affiche (ou enregistre) les paquets qui correspondent à un filtre. Le filtre est l'essentiel : sur un serveur actif, une capture sans filtre noie l'information et coûte cher en processeur. La syntaxe des filtres est décrite dans pcap-filter(7) :

$ sudo tcpdump -ni any 'host 172.16.20.5 and tcp port 8000'
$ sudo tcpdump -ni eth0 'tcp port 8000 and tcp[tcpflags] & (tcp-syn|tcp-rst) != 0'
$ sudo tcpdump -ni any -c 200 -w signalements.pcap 'tcp port 8000'
$ tcpdump -nr signalements.pcap
  • -n n'essaie pas de convertir les adresses et les ports en noms (ce qui ralentirait la capture et ajouterait des requêtes DNS à ce que l'on observe).
  • -i any écoute toutes les interfaces (pseudo-interface propre à Linux) ; -i eth0 une seule. Sur un répartiteur ou une passerelle, comparer deux interfaces dit si un paquet entre sans ressortir.
  • Le premier filtre ne garde que le trafic entre le répartiteur et le port 8000 ; le deuxième, les seuls segments SYN et RST (ouvertures et refus), ce qui suffit souvent et ne capture aucune donnée applicative.
  • -c 200 s'arrête après 200 paquets ; -w enregistre les paquets bruts dans un fichier, que l'on relit avec -r ou que l'on ouvre dans Wireshark sur son poste.

La page de manuel de tcpdump décrit le format d'une ligne TCP :

src > dst: Flags [tcpflags], seq data-seqno, ack ackno, win window, urg urgent, options [opts], length len

et l'illustre par le début d'une session, dont voici les quatre premières lignes, citées telles quelles :

IP rtsg.1023 > csam.login: Flags [S], seq 768512:768512, win 4096, opts [mss 1024]
IP csam.login > rtsg.1023: Flags [S.], seq 947648:947648, ack 768513, win 4096, opts [mss 1024]
IP rtsg.1023 > csam.login: Flags [.], ack 1, win 4096
IP rtsg.1023 > csam.login: Flags [P.], seq 1:2, ack 1, win 4096, length 1

On y reconnaît la poignée de main de la leçon 10 : [S] est un SYN, [S.] un SYN+ACK (le point note l'ACK), [.] un acquittement seul, [P.] des données avec PSH. Un [R] ou [R.] est un RST, [F.] un FIN. Après les premiers segments, tcpdump affiche des numéros de séquence relatifs (ack 1, seq 1:2) pour rester lisible ; l'option -S affiche les numéros absolus. Avec ces seules lettres, on lit la plupart des diagnostics : un [S] répété sans réponse est un filtrage ; un [S] suivi d'un [R.] est un refus ; une poignée de main complète suivie d'un silence met en cause l'application ou la taille des paquets.

En pratique

Les sorties de cette section ont été produites sur une machine Ubuntu 24.04, avec LC_ALL=C, sur l'interface locale ou sans qu'aucun paquet ne quitte la machine. tcpdump, qui exige les droits d'administration, n'y est pas exécuté : ses commandes sont décrites plus haut.

Qui écoute, et où

Sur le serveur, la première vérification est presque toujours celle-ci :

$ ss -ltnp 'sport = :8000'
State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0      5          127.0.0.1:8000      0.0.0.0:*    users:(("python3",pid=666826,fd=3))
  • -l les sockets en écoute, -t TCP, -n en chiffres, -p le processus propriétaire (pour un processus d'un autre compte, il faut sudo).
  • La colonne qui compte est Local Address : 127.0.0.1:8000 n'accepte que les connexions venues de la machine elle-même. Le répartiteur, qui arrive par 172.16.20.11, recevra un refus. Les formes attendues pour un service joignable depuis le réseau privé sont 172.16.20.11:8000 (l'adresse privée seule) ou 0.0.0.0:8000 (toutes les adresses IPv4).

Tester l'établissement de la connexion

Depuis le client, nc -vz répond à la question du milieu :

$ nc -vz 127.0.0.1 8000
Connection to 127.0.0.1 8000 port [tcp/*] succeeded!
  • -z établit la connexion puis la ferme sans rien envoyer ; -v affiche le résultat ; -w 3 fixerait un délai maximal de 3 secondes, indispensable quand on soupçonne un filtrage.

Quand la pile ne trouve aucune route, l'échec est local et immédiat. Cette machine n'a pas de route IPv6 par défaut ; une connexion vers une adresse de documentation IPv6 donne :

$ nc -vz -w 3 2001:db8::1 8000
nc: connect to 2001:db8::1 port 8000 (tcp) failed: Network is unreachable

Aucun paquet n'est parti : c'est la table de routage locale qui a répondu, ce que confirmerait ip route get 2001:db8::1. Une erreur de résolution, elle, survient avant même toute tentative :

$ python3 -c '
import socket
try: socket.getaddrinfo("sig-app-1.exemple.invalid", 8000)
except socket.gaierror as e: print(e.errno, e.strerror)'
-2 Name or service not known

Le domaine .invalid est réservé (RFC 6761) pour ne jamais exister : c'est le moyen sûr de provoquer cette erreur.

Distinguer un refus d'un délai, en mesurant

Le refus de la leçon 10 était immédiat (« after 0 ms »). Pour obtenir un délai sans quitter la machine, on peut reprendre la file accept saturée de la leçon 10 : un serveur avec un backlog de 0 qui n'appelle jamais accept(), déjà occupé par une connexion. Le SYN suivant est ignoré, comme il le serait par un pare-feu qui jette :

$ curl -sS --connect-timeout 3 http://127.0.0.1:8004/sante; echo "code=$?"
curl: (28) Failed to connect to 127.0.0.1 port 8004 after 3002 ms: Timeout was reached
code=28

--connect-timeout 3 limite l'attente de l'établissement à 3 secondes ; sans lui, curl attendrait la fin des retransmissions du SYN (environ deux minutes). Le code de sortie 28 signale un délai dépassé, le code 7 un échec de connexion.

Mesurer les phases d'une requête

Quand la connexion s'établit mais que « c'est lent », curl sait dire où le temps passe, avec l'option -w (write-out) et ses variables :

$ curl -sS -o /dev/null -w 'dns=%{time_namelookup} connexion=%{time_connect} tls=%{time_appconnect} premier_octet=%{time_starttransfer} total=%{time_total} code=%{http_code}\n' http://127.0.0.1:8000/sante
dns=0.000052 connexion=0.000331 tls=0.000000 premier_octet=0.006325 total=0.006401 code=200

Les temps sont en secondes, cumulés depuis le début de la requête :

  • time_namelookup : fin de la résolution du nom ;
  • time_connect : fin de la poignée de main TCP ; time_connect - time_namelookup est donc environ un RTT ;
  • time_appconnect : fin de la négociation TLS (0 en HTTP clair) ;
  • time_starttransfer : arrivée du premier octet de la réponse ; l'écart avec l'étape précédente est essentiellement le temps de traitement du serveur ;
  • time_total : fin du transfert.

Ici, sur l'interface locale, la connexion prend 0,3 ms et le serveur 6 ms : le réseau n'est pas en cause. Depuis le poste de l'équipe, à travers le répartiteur, la même mesure sépare en une commande un problème de DNS, de réseau, de TLS ou d'application.

Suivre le détail d'un échange

curl -v affiche chaque étape de la requête, préfixée par * (informations de curl), > (ce qui est envoyé) et < (ce qui est reçu) :

$ curl -sv http://127.0.0.1:8000/sante 2>&1
*   Trying 127.0.0.1:8000...
* Connected to 127.0.0.1 (127.0.0.1) port 8000
> GET /sante HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/8.5.0
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.12.3
< Date: Mon, 05 Oct 2026 09:48:45 GMT
< Content-type: application/octet-stream
< Content-Length: 3
< Last-Modified: Mon, 05 Oct 2026 09:39:32 GMT
<
{ [3 bytes data]
* Closing connection
ok

Le serveur de cet essai est python3 -m http.server, qui répond en HTTP/1.0. -s supprime la barre de progression ; 2>&1 réunit la trace (écrite sur la sortie d'erreur) et le corps de la réponse (ok, sur la sortie standard), et, les deux flux n'étant pas vidés au même moment, le corps apparaît ici en dernier. { [3 bytes data] note la réception des 3 octets du corps. La ligne Connected to prouve que TCP est établi ; si la commande s'arrête juste après, sans <, c'est le serveur qui ne répond pas, et non le réseau.

Le chemin : ip route get et mtr

ip route get <adresse> affiche la décision de routage que la machine prendrait pour cette destination, sans envoyer de paquet. La réponse tient en une ligne : l'adresse, puis via suivi de la passerelle (absent si la destination est sur un réseau directement connecté), dev suivi de l'interface de sortie, et src suivi de l'adresse source qui sera utilisée. C'est la commande qui répond à « par où part ce paquet, et avec quelle adresse ? », bien plus fiable que la lecture de la table entière.

mtr -n <destination> envoie des sondes en continu et affiche, pour chaque saut, le taux de pertes et les temps (moyen, meilleur, pire). Une perte qui apparaît à un saut et se propage à tous les suivants est réelle ; une perte affichée à un seul saut intermédiaire, mais pas aux suivants, vient le plus souvent d'un routeur qui limite ses réponses ICMP, et ne gêne pas le trafic qui le traverse.

Sous le capot

D'où viennent les messages. Quand un programme appelle connect(2), la pile choisit une route (sinon ENETUNREACH), résout l'adresse du prochain saut par ARP (sans réponse après quelques essais : EHOSTUNREACH), envoie le SYN et attend. Un RST en retour produit ECONNREFUSED ; un message ICMP « destination injoignable » est traduit selon son type (hôte, réseau, port, interdit administrativement) ; l'absence de toute réponse après les retransmissions produit ETIMEDOUT. La résolution de noms est faite avant, en espace utilisateur, par la bibliothèque C (getaddrinfo), qui renvoie ses propres codes (EAI_*) : c'est pourquoi une erreur de nom n'a pas de numéro errno.

Ce que voit tcpdump. La capture se fait au niveau du pilote de l'interface, par une socket spéciale (AF_PACKET) : tcpdump voit les paquets entrants avant le pare-feu de la machine, et les paquets sortants après que la pile les a construits. Un SYN visible dans la capture mais qui ne reçoit jamais de SYN+ACK a donc pu être jeté par le pare-feu local ; un SYN absent de la capture n'est jamais arrivé jusqu'à l'interface. Le filtre est compilé en un petit programme (BPF) exécuté dans le noyau, pour que seuls les paquets utiles soient copiés vers tcpdump.

Les ports éphémères comme ressource. Chaque connexion sortante vers une même destination (adresse et port) consomme un port local de la plage net.ipv4.ip_local_port_range, soit 28 232 ports de 32768 à 60999 par défaut. Un port reste occupé tant que la connexion existe, y compris en TIME-WAIT pendant 60 secondes. Un client qui ouvre et ferme plus de 28 000 connexions par minute vers la même destination épuise la plage : connect() échoue alors avec EADDRNOTAVAIL, « Cannot assign requested address ».

Pièges courants

Conclure de ping. Beaucoup de pare-feu et de groupes de sécurité bloquent ICMP. Un ping sans réponse ne prouve pas que la machine est injoignable en TCP ; un ping qui répond ne prouve pas que le port 8000 est ouvert. Testez le protocole et le port réellement utilisés, avec nc -vz ou curl.

Tester depuis le mauvais endroit. curl http://127.0.0.1:8000/sante sur le serveur ne teste ni le réseau, ni le pare-feu, ni l'adresse d'écoute. Testez depuis là où vient le trafic réel (le répartiteur, une autre instance du réseau privé), ou au moins vers l'adresse privée du serveur.

Désactiver le pare-feu « pour voir ». Si cela corrige, vous savez que le pare-feu est en cause, mais vous ne savez pas quelle règle, et la tentation de ne pas le réactiver est forte. Lisez plutôt les règles et leurs compteurs (sudo nft list ruleset affiche les compteurs des règles qui en ont), et ajoutez une règle précise.

Capturer sans filtre. Une capture tcpdump -i any sans filtre sur une machine active sature le terminal, perd des paquets (signalé à la fin par « packets dropped by kernel ») et enregistre au passage des données qui n'avaient rien à faire dans un fichier.

Oublier le chemin de retour. Un SYN peut arriver et sa réponse partir par une autre interface ou une autre passerelle, où un filtrage à état la jette faute d'avoir vu l'aller. Capturez des deux côtés, ou sur les deux interfaces d'un équipement.

Changer plusieurs choses à la fois. Le correctif qui « a marché » après trois modifications simultanées ne dit pas laquelle comptait ; les deux autres resteront, et l'une d'elles sera la cause du prochain incident.

Sécurité

  • Une capture contient des données. Tout ce qui passe en clair (HTTP entre le répartiteur et Gunicorn, requêtes SQL sans TLS) apparaît dans une capture : en-têtes d'authentification, cookies de session, données personnelles des signalements. Une capture est donc soumise aux mêmes règles que les données qu'elle contient, RGPD compris. Filtrez au plus juste (les seuls SYN et RST suffisent souvent), limitez la taille capturée par paquet (-s 96 ne garde que les en-têtes), et supprimez le fichier une fois l'analyse terminée.
  • Capturer est un privilège. tcpdump exige les capacités CAP_NET_RAW et CAP_NET_ADMIN, c'est-à-dire en pratique sudo. Ne donnez pas ces capacités de façon permanente au binaire, et ne laissez pas un groupe d'utilisateurs capturer librement : qui capture voit les secrets des autres. tcpdump abandonne d'ailleurs ses privilèges après avoir ouvert l'interface (option -Z, compte tcpdump par défaut sur Debian et Ubuntu).
  • Chiffrez le dernier saut. Que le trafic entre le répartiteur et les instances soit lisible par quiconque capture sur le réseau privé est un choix, pas une fatalité ; le cours TLS et PKI montre comment le chiffrer de bout en bout quand les exigences l'imposent.
  • Ne laissez pas d'ouverture de diagnostic. Une règle « temporaire » ouverte à 0.0.0.0/0 pendant un incident, un nc -l oublié qui écoute sur un port, un service lancé à la main sur 0.0.0.0 : faites-en l'inventaire en fin d'incident, avec ss -ltnp et la liste des règles.

En production

  • Préparez les points d'observation. La dichotomie suppose de pouvoir regarder au milieu du chemin : journaux et métriques du répartiteur (état des backends, codes de réponse), compteurs TCP des instances (nstat), vérifications de santé. Une plateforme qui n'expose rien oblige à tout deviner.
  • Écrivez les diagnostics courants dans un runbook. Les cinq scénarios ci-dessous, avec leurs commandes, valent mieux dans la documentation de l'équipe que dans la mémoire d'une personne. Le cours Réduire le toil : runbooks et automatisation en fait une pratique.
  • Faites tester le chemin réel par la supervision. Une sonde qui appelle https://signalements.exemple.fr/sante depuis l'extérieur, et une autre qui teste chaque backend depuis le réseau privé, disent tout de suite quelle moitié du chemin est en cause.
  • Gardez une trace. Notez pendant l'incident ce que vous avez vérifié, ce que vous avez changé et à quelle heure. Le postmortem (cours Postmortems sans reproche) se rédige à partir de ces notes.

Exercices

Les exercices de cette leçon sont cinq pannes de Signalements. Pour chacune, formulez votre hypothèse et les commandes qui la vérifient avant d'ouvrir la solution. Rappel des adresses : sig-app-1 172.16.20.11, sig-app-2 172.16.20.12, répartiteur 172.16.20.5 côté privé, base 172.16.20.30:5432, réseau privé 172.16.20.0/22.

1. Le backend hors service (niveau 200). Après une mise à jour, le répartiteur marque sig-app-1 hors service. Sur sig-app-1, systemctl status signalements est actif et curl http://127.0.0.1:8000/sante répond 200. Depuis sig-app-2, nc -vz 172.16.20.11 8000 répond immédiatement « Connection refused ».

Solution

Le refus est immédiat : la machine est joignable, mais rien n'écoute sur 172.16.20.11:8000. ss -ltnp 'sport = :8000' sur sig-app-1 montre 127.0.0.1:8000 : la mise à jour a changé l'option --bind de Gunicorn (ou la variable qui la fixe). Correction : lier Gunicorn à l'adresse privée (--bind 172.16.20.11:8000) ou à 0.0.0.0:8000 si le pare-feu de l'hôte protège le port, redémarrer, puis vérifier depuis sig-app-2 avec nc -vz et attendre que le répartiteur repasse le backend en service. Le test depuis 127.0.0.1 ne pouvait pas révéler cette panne.

2. Le silence (niveau 200). Depuis ce matin, toutes les requêtes vers sig-app-1 échouent par délai dépassé ; sig-app-2 fonctionne. Le service écoute bien sur 172.16.20.11:8000. Hier soir, une personne a « durci » le pare-feu de l'hôte.

Solution

Délai dépassé avec un service qui écoute sur la bonne adresse : un filtrage jette les paquets. On le confirme par une capture des seuls SYN et RST sur sig-app-1 : sudo tcpdump -ni any 'tcp port 8000 and tcp[tcpflags] & (tcp-syn|tcp-rst) != 0'. Des [S] venus de 172.16.20.5 apparaissent, répétés, sans [S.] en réponse : les paquets arrivent sur l'interface (tcpdump les voit avant le pare-feu) et sont jetés par le pare-feu local. sudo nft list ruleset montre la règle fautive (politique drop en entrée sans exception pour 172.16.20.0/22 sur le port 8000). On ajoute une règle précise, on vérifie, et l'on documente.

3. La base injoignable depuis une seule instance (niveau 200). sig-app-2, recréée hier, ne joint plus sig-db : psql échoue par délai dépassé, alors que sig-app-1 fonctionne. ip -br addr sur sig-app-2 montre l'adresse 172.16.24.12/22 sur l'interface privée.

Solution

172.16.24.12/22 n'appartient pas au réseau 172.16.20.0/22 (qui va de 172.16.20.0 à 172.16.23.255) : sig-app-2 a été rattachée à un autre réseau privé, ou ce réseau a été recréé avec une autre plage (leçon 4). ip route get 172.16.20.30 montre que le paquet part par la route par défaut (vers la passerelle) au lieu de l'interface privée, ou qu'il n'y a pas de chemin. Et même si un chemin existait, la base n'accepte peut-être que les connexions de son réseau. Correction : rattacher sig-app-2 au bon réseau privé, vérifier son adresse et la route, puis tester avec nc -vz -w 3 172.16.20.30 5432 avant psql.

4. Les petites requêtes passent, les grosses non (niveau 200). Depuis le poste d'un agent en télétravail, à travers le VPN de la mairie, GET /sante fonctionne, mais l'envoi d'un signalement avec une photo reste bloqué indéfiniment. Depuis le bureau, tout fonctionne.

Solution

Les petits paquets passent, les gros disparaissent, et seulement sur un chemin qui ajoute une encapsulation (le VPN) : c'est le trou noir MTU de la leçon 6. Le tunnel réduit la MTU du chemin, un routeur jette les paquets trop gros avec le drapeau « ne pas fragmenter », et le message ICMP qui devrait en avertir l'émetteur est filtré quelque part. La poignée de main et les petites requêtes passent ; les segments pleins de la photo, jamais. Vérification : tracepath vers le service depuis le poste, ou ping -M do -s 1400 puis des tailles décroissantes. Corrections : laisser passer les messages ICMP « fragmentation nécessaire » (ICMPv6 « paquet trop gros » en IPv6), ou réduire la MSS annoncée sur l'équipement du tunnel (MSS clamping).

5. « Cannot assign requested address » (niveau 200). Un script de reprise de données envoie un signalement par requête à l'API interne de sig-app-1, en ouvrant une nouvelle connexion HTTP à chaque fois, environ 600 par seconde. Au bout d'une minute, il échoue avec Cannot assign requested address. ss -s sur la machine du script montre près de 28 000 connexions en TIME-WAIT.

Solution

Le script épuise ses ports éphémères : chaque connexion vers 172.16.20.11:8000 consomme un port local, qui reste occupé 60 secondes en TIME-WAIT après la fermeture (le script ferme en premier, leçon 10). 600 connexions par seconde pendant 60 secondes font 36 000 ports, plus que les 28 232 de la plage par défaut : connect() échoue avec EADDRNOTAVAIL. La vraie correction est de réutiliser les connexions (une session HTTP avec keep-alive, comme requests.Session en Python) : quelques connexions suffisent alors pour tout le débit, et le gain de latence est important (leçon 11). Élargir ip_local_port_range ou activer tcp_tw_reuse ne fait que repousser le problème.

Récapitulatif

  • Un diagnostic est une suite d'hypothèses vérifiées : décrire précisément le symptôme, avancer couche par couche ou par dichotomie, ne changer qu'une chose à la fois.
  • Les questions dans l'ordre : nom, route (ip route get), lien et voisin (ip neigh), écoute sur le bon port et la bonne adresse (ss -ltnp), filtrage, établissement TCP (nc -vz), réponse de l'application (curl -v, curl -w).
  • Les messages disent qui a répondu : refused (un RST de la destination), timed out (personne), No route to host (ICMP ou ARP), Network is unreachable (la pile locale), Name or service not known (la résolution, avant toute connexion).
  • tcpdump -n avec un filtre précis est l'arbitre final ; [S] répétés sans réponse : filtrage ; [S] puis [R.] : refus ; poignée de main puis silence : application ou MTU.
  • Les ports éphémères (28 232 par défaut) s'épuisent avec les connexions jetables : Cannot assign requested address, à corriger en réutilisant les connexions.
  • Une capture contient des données : filtrer, tronquer, supprimer, et réserver ce droit à peu de personnes.

Pour aller plus loin

  • Les pages de manuel tcpdump(1) et pcap-filter(7), dont les exemples couvrent la plupart des filtres utiles.
  • La méthode USE de Brendan Gregg (utilisation, saturation, erreurs), qui donne un ordre de questions applicable aux ressources réseau comme aux autres.
  • TCP/IP Illustrated, Volume 1, pour s'entraîner à lire des captures commentées.
  • Le cours Le réseau sous Linux : nftables, namespaces, bridges, pour construire et diagnostiquer des réseaux virtuels sur une seule machine, et le cours Performance et diagnostic sous Linux pour aller plus loin dans la mesure.
  • Le lab du cours, qui fait monter un petit réseau dans une machine virtuelle, puis le casser pour observer un refus, un délai dépassé et un problème de MTU.
Voir ma constellation →

Sources