Aller au contenu principal

Suivre une requête réseau : DNS, connexions, TLS et HTTP

Lorsqu’un site ne se charge pas, cherchez jusqu’où la requête est arrivée : le nom d’hôte a-t-il été résolu, la connexion établie, la négociation TLS réussie, une réponse HTTP reçue ? Un code 404 et un port injoignable appellent des recherches différentes. La requête peut aussi passer par un proxy : le serveur auquel le navigateur se connecte n’est alors pas forcément le programme qui réalise l’opération métier.

Prenons l’URL hypothétique https://api.example.com:443/health?full=1 pour suivre ce parcours. https indique un accès HTTP sécurisé, api.example.com est le nom d’hôte, 443 le port, /health le chemin et full=1 la chaîne de requête. Sans port explicite, HTTPS utilise par défaut 443, et HTTP 80. Cette convention vient du schéma de l’URL ; les enregistrements DNS A/AAAA ne transportent pas ces ports. RFC 9110 §4.2

1. Résoudre le nom d’hôte et comprendre le cache​

Le DNS associe les noms de domaine à des enregistrements typés. L’application s’adresse généralement à l’interface de résolution du système, qui peut transmettre la demande à un résolveur récursif configuré. Si les informations locales ne suffisent pas, ce résolveur suit les délégations dans l’arbre des domaines jusqu’aux serveurs faisant autorité pour les données recherchées. Une réponse déjà en cache peut éviter une partie de ces échanges. RFC 1034 §2.4 et §5.3

Pour rechercher une adresse, un enregistrement A fournit une adresse IPv4, et un enregistrement AAAA une adresse IPv6, définie dans la RFC 3596 §2. Un même nom d’hôte peut avoir plusieurs adresses. Il faut ensuite tenter une connexion pour savoir si une adresse est joignable depuis le réseau actuel.

Le TTL, ou durée de vie, se mesure en secondes et indique normalement combien de temps un enregistrement en cache peut être utilisé avant de consulter à nouveau sa source. Il s’applique aux copies conservées, sans imposer que tous les clients changent d’adresse au même instant. RFC 1034 §3.6 Un résolveur configuré pour servir des données périmées peut continuer à renvoyer des enregistrements expirés lorsqu’il ne parvient pas à les rafraîchir auprès des serveurs faisant autorité. RFC 8767 §4

Supposons qu’un résolveur mette l’ancienne adresse en cache à 12:00:00 avec un TTL de 300 secondes. À 12:01:00, le serveur faisant autorité change l’adresse et attribue au nouvel enregistrement un TTL de 60 secondes. Le résolveur ne rafraîchit pas sa copie à l’avance et n’applique aucune politique supplémentaire de service des données expirées :

HeureÉvénementAncien enregistrement dans ce résolveur
12:00:00Réception de l’ancienne adresse avec un TTL de 300Mise en cache jusqu’à 12:05:00
12:01:00Modification des données faisant autoritéLa copie existante conserve son compte à rebours initial de 300 secondes
12:02:00Nouvelle demande du client120 secondes se sont écoulées ; il reste 300 - 120 = 180 secondes, donc l’ancienne adresse peut être renvoyée
12:05:00Expiration du TTL initialIl reste 0 seconde ; la prochaine demande exige de nouvelles données valides

Réduire le TTL au moment du changement ne raccourcit pas un TTL déjà distribué. Pour préparer une migration, réduisez-le à l’avance et laissez le temps à l’ancien TTL d’expirer. Les résolveurs alimentent leur cache à des moments différents. Pour enquêter, relevez le nom d’hôte, le type d’enregistrement, le résolveur interrogé, l’adresse obtenue et le TTL restant.

L’interface de résolution du système peut aussi utiliser une configuration locale. En Python, socket.getaddrinfo renvoie les informations d’adresse nécessaires à une connexion, sans TTL DNS. L’expérience ci-dessous résout localhost par cette interface : elle montre la résolution locale d’un nom, pas un échange DNS public. Le cache DNS conserve des enregistrements de noms ; le cache HTTP conserve des réponses. Recharger une page ne prouve donc pas que le cache DNS a été vidé. Modèle de cache HTTP, RFC 9111

2. Adresses, ports, écoute et connexions​

Une adresse IP dirige les données vers un point du réseau ; un port aide le système d’exploitation à distinguer les services. Un serveur TCP lie un socket à une adresse et à un port, puis se met à l’écoute des nouvelles connexions. Une liaison à 127.0.0.1 accepte les connexions par la boucle locale IPv4 ; 0.0.0.0 couvre toutes les interfaces IPv4 locales. Cette dernière adresse sert de joker pour l’écoute : le client utilise une adresse réellement joignable. Documentation des sockets IPv4 sous Linux L’accès extérieur dépend aussi du routage, des pare-feu et des redirections de ports.

Une connexion TCP est identifiée par deux extrémités, chacune comprenant une adresse et un port. Imaginons un client qui se connecte de 192.0.2.20:53000 à 203.0.113.10:443. Le port 53000 appartient à cette connexion côté client ; 443 est le port du service. D’autres clients peuvent contacter le même port de destination 443, tout en restant distingués par le système. TCP fournit un flux d’octets fiable et ordonné, avec des accusés de réception et des retransmissions pour traiter les pertes. Une ouverture ordinaire passe par SYN, SYN-ACK et ACK. RFC 9293 §2.2 et §3.5

Une connexion établie rend le chemin de transport utilisable. L’accusé de réception TCP des octets ne prouve toutefois pas que l’application a analysé la requête, vérifié les permissions ou validé une transaction en base de données. Si la connexion tombe après l’envoi d’une commande, le client peut ignorer si celle-ci a été créée. Il ne devrait pas répéter automatiquement une requête non idempotente, sauf s’il sait que sa sémantique réelle permet une répétition sans effet supplémentaire ou peut établir que la première requête n’a jamais été appliquée. L’idempotence signifie que répéter la requête produit le même effet attendu qu’une seule exécution. RFC 9110 §9.2.2

Ce parcours TCP correspond à l’expérience HTTP/1.1 ci-dessous et à l’usage courant de HTTPS sur TCP. HTTP/3 utilise QUIC sur UDP avec une négociation TLS. Pour diagnostiquer HTTP/3, vérifiez le chemin UDP : un port TCP 443 joignable ne suffit pas à établir que ce transport fonctionne. RFC 9114 §3

3. TLS : vérifier le correspondant et protéger le canal​

Lors d’une nouvelle connexion HTTPS utilisant des certificats, la négociation TLS établit les clés ; le serveur présente son certificat et prouve qu’il possède la clé privée correspondante. TLS 1.3 vise l’authentification, la confidentialité et l’intégrité : une fois le canal établi, seules ses extrémités peuvent lire les données, et une modification hostile est détectable. La longueur des données peut rester visible. RFC 8446 §1 et §4.4.3

Le client doit vérifier deux points distincts : la validité du certificat, notamment sa chaîne de confiance et sa période de validité (RFC 5280 §6.1), et la correspondance de son identité avec l’hôte recherché. Pour api.example.com, la correspondance se fait avec les noms DNS de l’extension subjectAltName. Le client ne doit pas remplacer l’identité attendue par un nom arbitraire fourni par le serveur. Un accès par adresse IP exige l’identifiant IP correspondant ; le certificat d’un domaine hébergé sur la même machine ne suffit pas à lui seul. RFC 9525 §6

SNI, Server Name Indication, permet au client d’indiquer l’hôte recherché pendant la négociation TLS, afin que le serveur puisse choisir un certificat. Cette étape précède le champ HTTP Host. RFC 6066 §3 Dans un test, remplacer le nom d’hôte par une adresse IP peut modifier SNI, la vérification du certificat et le routage HTTP, même si la connexion atteint la même destination. Comparez ces éléments séparément.

TLS protège le canal entre ses deux extrémités. Un proxy inverse qui termine TLS peut lire le contenu HTTP ; la connexion entre ce proxy et le serveur applicatif nécessite une évaluation distincte du chiffrement. Après vérification du certificat, l’application doit encore contrôler les identifiants de l’utilisateur et les permissions métier. Les résultats DNS et les connexions établies peuvent être réutilisés : chaque requête HTTP n’exige donc pas une nouvelle résolution, une nouvelle connexion et une négociation TLS complète.

4. HTTP : méthodes, codes d’état, en-têtes et corps​

Une requête HTTP porte une méthode et une cible ; une réponse porte un code d’état. Les deux peuvent comprendre des en-têtes et un corps. Les lignes textuelles de HTTP/1.1 et les trames de HTTP/2 ou HTTP/3 expriment différemment les messages, tout en partageant cette sémantique de base. RFC 9110 §6

MéthodeOpération demandée
GETObtenir la représentation actuellement sélectionnée d’une ressource, par exemple des données JSON
HEADComme GET, sans transmettre le corps de la réponse
POSTFaire traiter le contenu envoyé selon les règles de la ressource, par exemple pour créer une commande
PUTCréer ou remplacer l’état de la ressource cible par la représentation envoyée
DELETERetirer l’association entre la ressource cible et sa fonction actuelle ; l’effacement physique du stockage n’est pas garanti

Ces définitions figurent dans la RFC 9110 §9.3. Considérez ensemble la méthode et le chemin : un chemin peut accepter POST et refuser GET.

Les en-têtes précisent le sens du message. Host — souvent :authority en HTTP/2 et HTTP/3 — identifie l’hôte cible et son port éventuel. Content-Type indique le type de média du corps. Content-Length compte les octets, pas les caractères, et permet de délimiter les réponses HTTP/1.1 de l’expérience. Authorization transporte les informations d’authentification. Déclarer un type JSON ne dispense pas le corps de respecter le contrat de l’API. RFC 9110 §7.2, §8.3, §8.6 et §11.6.2

Le premier chiffre du code distingue les réponses informatives 1xx, les succès 2xx, les redirections 3xx, les erreurs client 4xx et les erreurs serveur 5xx. Pour diagnostiquer un problème, lisez le code précis et le corps. RFC 9110 §15

RéponseCe qu’elle indique
200La requête a réussi au sens HTTP ; vérifiez que le corps contient le résultat attendu
202Le traitement a été accepté, mais n’est pas terminé ; son exécution et sa réussite ne sont pas garanties
401Les informations d’authentification valides manquent ; la réponse doit contenir un défi WWW-Authenticate
403La requête a été comprise mais refusée ; les identifiants ne sont pas forcément en cause
404Aucune représentation actuelle n’a été trouvée, ou le serveur refuse d’en révéler l’existence
405Le serveur reconnaît la méthode, mais la ressource cible ne la prend pas en charge ; Allow doit énumérer les méthodes actuellement acceptées par cette cible
500Une situation imprévue côté serveur a empêché de terminer la requête
502 / 504Une passerelle a reçu une réponse amont invalide / n’a pas reçu à temps la réponse amont nécessaire

Après réception des en-têtes, vérifiez que le corps est complet. La connexion peut tomber au milieu de la réponse, ou le JSON être impossible à analyser. Il s’agit de pannes ultérieures ; un code d’état ne remplace pas un résultat métier, ni l’inverse.

5. Proxys et localhost : situer l’environnement du client​

Un proxy direct envoie des requêtes pour le compte du client. Un proxy inverse, ou passerelle, reçoit les requêtes des clients et les transmet à un serveur applicatif. RFC 9110 §3.7 Supposons que le point d’entrée public termine HTTPS et utilise HTTP vers l’application. Le parcours comporte deux connexions indépendantes :

ConnexionDestinationQui vérifie quoi
Navigateur vers le point d’entréeAdresse publique, port 443Le navigateur vérifie le certificat présenté pour le domaine demandé
Point d’entrée vers l’applicationAdresse du serveur applicatif, port 8080Le point d’entrée vérifie la réponse applicative ; cette liaison HTTP n’est pas chiffrée par TLS

Si le navigateur reçoit un 502 du point d’entrée, il a obtenu une réponse HTTP sur la première liaison, alors que le serveur applicatif peut être en panne. Distinguez la connexion du client au point d’entrée de celle du point d’entrée à l’application, et consultez les journaux du point d’entrée pour localiser la défaillance.

localhost désigne la boucle locale de l’environnement qui ouvre la connexion, couramment IPv4 127.0.0.1 ou IPv6 ::1. Les bibliothèques de résolution devraient traiter ce nom spécialement, normalement sans interroger le DNS public. RFC 6761 §6.3 Un navigateur sur un ordinateur portable qui ouvre localhost:8080 contacte un service de cet ordinateur, pas celui d’un VPS distant.

Dans un conteneur, identifiez aussi l’espace de noms réseau : l’ensemble des interfaces et des routes visibles par le processus. Avec un espace de noms distinct, 127.0.0.1 désigne le conteneur lui-même ; le partage de la pile réseau de l’hôte ou d’un autre conteneur modifie cette portée. Documentation réseau de Docker Un proxy dans un conteneur séparé ne peut donc pas utiliser son propre localhost pour atteindre automatiquement le conteneur applicatif. Il lui faut une adresse ou un nom de service joignable.

Pour la configuration, poursuivez avec la vue d’ensemble du VPS et son architecture et sa sécurité. Cloudflare Workers explique comment un environnement géré reçoit les requêtes et renvoie les réponses. MCP décrit les messages d’outils et l’autorisation lors de l’utilisation de son transport HTTP.

6. Expérience locale : une connexion fonctionnelle peut renvoyer 404​

Enregistrez le code ci-dessous dans request_trace.py, puis exécutez python3 request_trace.py. Il utilise uniquement la bibliothèque standard de Python, démarre un service HTTP temporaire sur la boucle locale IPv4, envoie deux requêtes GET, puis tente de joindre un port lié à un socket mais sans écoute. Le port serveur est fixé à 0 pour laisser le système choisir un port disponible ; les numéros peuvent changer d’une exécution à l’autre.

Le serveur utilise http.server, et le client http.client. Cette expérience emploie HTTP en clair pour montrer la résolution locale, TCP et HTTP. Les contrôles du certificat et la négociation TLS correspondent au parcours HTTPS décrit plus haut.

Ce gestionnaire implémente uniquement GET : /health renvoie 200, et les autres chemins renvoient 404. BaseHTTPRequestHandler.path comprend la chaîne de requête ; urlsplit sépare donc le chemin des paramètres. Cette expérience ignore les paramètres de requête : /health?full=1 renvoie le même résultat de contrôle de santé.

import socket
from http.client import HTTPConnection
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from threading import Thread
from urllib.parse import urlsplit


class Handler(BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"

def do_GET(self):
if urlsplit(self.path).path == "/health":
status, body = 200, b'{"ok":true}\n'
else:
status, body = 404, b'{"error":"not_found"}\n'
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.send_header("Cache-Control", "no-store")
self.end_headers()
self.wfile.write(body)

def log_message(self, format, *args):
pass


server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
port = server.server_address[1]
worker = Thread(target=server.serve_forever, daemon=True)
worker.start()
conn = HTTPConnection("localhost", port, timeout=2)
try:
results = socket.getaddrinfo(
"localhost", port, socket.AF_INET, socket.SOCK_STREAM
)
print("resolve localhost (IPv4):", sorted({r[4][0] for r in results}))
print(f"listen: 127.0.0.1:{port}")
conn.connect()
print(f"TCP: {conn.sock.getsockname()} -> {conn.sock.getpeername()}")
for path in ("/health", "/missing"):
conn.request("GET", path, headers={"Host": f"localhost:{port}"})
response = conn.getresponse()
body = response.read()
print(f"GET {path} -> {response.status}; "
f"type={response.getheader('Content-Type')}; "
f"length={response.getheader('Content-Length')}; read={len(body)}")
print(body.decode("utf-8"), end="")
finally:
conn.close()
server.shutdown()
server.server_close()
worker.join()

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as unused:
unused.bind(("127.0.0.1", 0))
try:
with socket.create_connection(unused.getsockname(), timeout=2):
print("unexpected connection")
except (ConnectionRefusedError, TimeoutError) as error:
print("bound port without listen:", type(error).__name__)

Sortie d’une exécution avec Python 3.13.15 :

resolve localhost (IPv4): ['127.0.0.1']
listen: 127.0.0.1:64242
TCP: ('127.0.0.1', 64244) -> ('127.0.0.1', 64242)
GET /health -> 200; type=application/json; length=12; read=12
{"ok":true}
GET /missing -> 404; type=application/json; length=22; read=22
{"error":"not_found"}
bound port without listen: TimeoutError

64242 est le port d’écoute du serveur ; 64244 est le port source du client pour cette connexion. Les deux requêtes réutilisent cette connexion et n’envoient aucun corps. Les corps des réponses contiennent respectivement 12 et 22 octets UTF-8, saut de ligne final compris. length est la taille déclarée par le serveur ; read la taille réellement reçue. Le 404 de /missing montre que l’échange HTTP fonctionne : recherchez le problème dans le chemin de la ressource plutôt que dans l’établissement de la connexion.

La dernière tentative a expiré lors de cette exécution ; un autre système peut la refuser immédiatement. Aucun de ces résultats ne comporte de code HTTP. Ici, l’expérience établit que le port n’est pas à l’écoute, ce qui permet d’en connaître la cause. Sur un réseau réel, un délai dépassé ne suffit pas à distinguer un problème d’écoute d’un problème de pare-feu ou de routage.

7. Diagnostiquer à partir de la dernière couche fonctionnelle​

Relevez l’environnement qui lance la requête, l’URL, le correspondant réellement joint, la dernière étape réussie et le message d’erreur exact. Vérifiez chaque étape depuis le même client, sans réunir artificiellement les observations du portable, du conteneur et du serveur en un seul parcours.

ObservationVérifications suivantes
La résolution échoue ou renvoie une adresse inattendueOrthographe du nom, A/AAAA, résolveur actuel, configuration locale et caches ; le port cible n’est pas encore prouvé joignable
La connexion TCP est refuséeAdresse et port, présence d’une écoute et règles de rejet actif
La tentative de connexion expireRoutage, pertes, pare-feu et adresse d’écoute sur cette liaison précise
La connexion s’établit, mais TLS échoueService réellement TLS, chaîne du certificat, validité, horloge locale, nom attendu et SNI
Réception de 401, 403, 404 ou 405Authentification, permissions, routage par hôte, chemin et méthode ; lire d’abord les en-têtes et le corps
Réception de 502 ou 504Émetteur de la réponse, serveur applicatif contacté par ce proxy, connexion et traitement côté application
Code normal, mais corps incomplet ou résultat métier incorrectDélimitation du message, analyse du corps, contrat de l’API et journaux métier ; vérifier si une écriture a déjà eu lieu avant de répéter la requête

Un contrôle de santé qui renvoie 200 établit que le chemin testé fonctionne. Si la tâche consiste à créer une commande, vérifiez aussi l’authentification de cette API, ses entrées et le résultat enregistré. Chaque étape doit répondre à une question précise avant de passer aux couches suivantes.

Explorer les liensOuvrir le réseau