Aller au contenu
Pourquoi des couches

Pourquoi des couches

100 Comprendre ⏱ 50 min reseaulinuxtcp

À la fin, vous saurez

  • Nommer les quatre couches du modèle TCP/IP et dire ce que chacune apporte
  • Décrire l'encapsulation d'une requête HTTP jusqu'à la trame Ethernet, avec la taille de chaque en-tête
  • Employer correctement trame, paquet, datagramme et segment
  • Situer le modèle OSI par rapport au modèle TCP/IP et comprendre ce que veut dire « couche 4 » ou « couche 7 »
  • Expliquer le principe de bout en bout et ce qu'il implique pour la conception d'une application
  • Observer une connexion TCP locale avec ss et /proc/net/tcp, et lire une RFC

Prérequis

Testé avec curl 8.5.0 iproute2 6.1.0 noyau 7.0 (HWE) python 3.12.3 ubuntu 24.04 , vérifié le 5 octobre 2026

Pourquoi

Camille, développeuse dans l'équipe qui exploite Signalements, tape dans son terminal :

$ curl http://203.0.113.10/sante
{"etat": "ok"}

Une ligne part, une ligne revient, en quelques dizaines de millisecondes. Entre les deux, la requête a quitté un ordinateur portable branché en Wi-Fi sur la box d'un appartement, traversé le réseau de son fournisseur d'accès, plusieurs réseaux d'opérateurs, l'entrée du centre de données de Scaleway, un répartiteur de charge, un réseau privé virtuel, et atteint un processus Gunicorn sur la machine sig-app-1. Chacun de ces réseaux utilise un matériel différent, a été construit par une équipe différente, à une époque différente. Aucun ne sait rien des autres. Et pourtant, cela marche, des milliards de fois par seconde à l'échelle de la planète.

Le jour où cela ne marche plus, Camille voit un message : Connection refused, Connection timed out, No route to host, Could not resolve host. Ces quatre erreurs ne viennent pas du même endroit et ne se diagnostiquent pas de la même façon. Sans une image claire de ce qui se passe entre le programme et le câble, on essaie des choses au hasard : on redémarre le service, on désactive le pare-feu, on change de réseau Wi-Fi. Avec cette image, on sait à quel étage chercher.

Cette image, c'est le modèle en couches. Il est l'idée d'organisation la plus importante des réseaux, et ce cours entier s'en sert comme d'une carte. Cette leçon la pose, suit la requête de Camille octet par octet, et montre où ces couches vivent dans une machine Linux.

Les concepts

Le problème à résoudre

Faire communiquer deux programmes à travers un réseau demande de résoudre une liste de problèmes très différents :

  • transformer des bits en signal électrique, en lumière ou en onde radio, et inversement ;
  • désigner une machine parmi toutes celles qui partagent un même câble ou une même borne Wi-Fi ;
  • trouver un chemin entre deux machines qui ne sont pas sur le même réseau ;
  • livrer les données au bon programme parmi tous ceux qui tournent sur la machine ;
  • remettre les données dans l'ordre, retransmettre ce qui s'est perdu, ne pas noyer un destinataire plus lent ;
  • donner un sens aux octets : une requête, une réponse, un code d'état.

Un seul programme qui ferait tout cela serait ingérable, et devrait être réécrit à chaque nouveau type de câble. L'idée des couches est de découper : chaque couche résout une partie du problème, rend un service à la couche du dessus, et utilise le service de la couche du dessous sans savoir comment il est rendu. Le navigateur ignore s'il est en Wi-Fi ou en fibre ; la carte Wi-Fi ignore si elle transporte du HTTP ou de la vidéo.

Les quatre couches du modèle TCP/IP

La description de référence de l'architecture d'Internet est la RFC 1122, publiée en octobre 1989 sous la direction de Robert Braden. Elle décrit ce qu'un hôte (une machine qui n'est pas un routeur) doit implémenter, et l'organise en quatre couches :

CoucheRôleProtocoles typiquesCe qu'elle désigne
Applicationle sens des échangesHTTP, DNS, SSH, SMTPune ressource, un nom
Transportune communication de bout en bout entre deux programmesTCP, UDPun port
Internetacheminer un paquet d'un réseau à l'autre, jusqu'à la machine de destinationIP (v4 et v6), ICMPune adresse IP
Lientransmettre une trame à une machine voisine, sur un même réseau localEthernet, Wi-Fi (802.11)une adresse MAC

Retenez la dernière colonne : chaque couche a son propre système d'adresses, et l'une des tâches du réseau est de passer de l'un à l'autre (un nom vers une adresse IP avec le DNS, une adresse IP vers une adresse MAC avec ARP, leçon 2).

Deux couches méritent qu'on insiste :

  • La couche internet est la seule qui traverse tout. Un routeur lit l'en-tête IP pour décider où envoyer le paquet ; il ne regarde normalement ni le port, ni le contenu HTTP. IP est un service sans connexion et sans garantie : chaque paquet voyage seul, peut se perdre, arriver en double ou dans le désordre. La RFC 1122 parle de service de datagrammes « au mieux » (best effort).
  • La couche transport n'existe que sur les deux machines aux extrémités. C'est là que TCP reconstruit, à partir de paquets non fiables, un flux d'octets fiable et ordonné (leçons 10 et 11). Les routeurs du milieu n'y participent pas.

L'encapsulation

Comment une couche « utilise le service » de celle du dessous ? En confiant ses données à la couche inférieure, qui les enveloppe dans son propre en-tête, puis les confie à son tour à la couche du dessous. C'est l'encapsulation : chaque couche traite tout ce qu'elle reçoit d'en haut comme une charge utile opaque, et n'ajoute que ce dont elle a besoin.

    flowchart TB
  A["Application : GET /sante HTTP/1.1 ... (82 octets)"]
  T["Transport : en-tête TCP | données HTTP"]
  I["Internet : en-tête IP | en-tête TCP | données HTTP"]
  L["Lien : en-tête Ethernet | en-tête IP | en-tête TCP | données HTTP | FCS"]
  A --> T --> I --> L
  

À l'arrivée, le chemin est inverse : chaque couche lit son en-tête, décide à qui confier le reste, et le retire. On parle de désencapsulation. Pour décider « à qui confier le reste », chaque en-tête contient un champ qui désigne le protocole du dessus :

  • l'en-tête Ethernet contient un EtherType (0x0800 pour IPv4, 0x86DD pour IPv6, 0x0806 pour ARP) ;
  • l'en-tête IP contient un numéro de protocole (6 pour TCP, 17 pour UDP, 1 pour ICMP) ;
  • l'en-tête TCP contient un port de destination, qui désigne le programme (8000 pour Gunicorn).

Ces valeurs sont visibles dans les en-têtes C du noyau Linux, que l'on trouve sur toute machine où les en-têtes de développement sont installés :

$ grep -E "define ETH_P_(IP|ARP|IPV6)\b" /usr/include/linux/if_ether.h
#define ETH_P_IP	0x0800		/* Internet Protocol packet	*/
#define ETH_P_ARP	0x0806		/* Address Resolution packet	*/
#define ETH_P_IPV6	0x86DD		/* IPv6 over bluebook		*/

Les noms des unités

Chaque couche a un nom pour ce qu'elle transmet. La RFC 1122 les définit, et les employer correctement évite des malentendus dans un ticket d'incident :

CoucheUnitéDéfinition (RFC 1122, section 1.3.3)
Lientrame (frame)en-tête de lien suivi d'un paquet
Internetpaquet (packet) ou datagramme IPen-tête IP suivi de données (un datagramme entier ou un fragment)
Transport, TCPsegmenten-tête TCP suivi de données de l'application
Transport, UDPdatagramme (UDP)en-tête UDP suivi de données
Applicationmessagece que l'application envoie, ici une requête HTTP

Dans la conversation courante, on dit souvent « paquet » pour tout. Ce n'est pas grave tant que l'on sait, en cas de besoin, préciser : « la trame est arrivée, mais le paquet a été rejeté par le pare-feu » décrit une situation exacte.

Ce que pèsent les en-têtes

Chaque enveloppe a un coût. Les tailles minimales à retenir :

En-têteTailleSource
Ethernet14 octets (2 adresses MAC de 6 octets, 2 octets d'EtherType), plus 4 octets de somme de contrôle (FCS) à la finif_ether.h : ETH_HLEN 14, ETH_FCS_LEN 4
IPv420 octets sans optionsRFC 791
IPv640 octets, taille fixeRFC 8200
TCP20 octets sans options ; 32 octets sous Linux avec l'option d'horodatage, active par défautRFC 9293, tcp(7)
UDP8 octetsRFC 768

Sur un câble Ethernet, chaque trame est en outre précédée d'un préambule de 8 octets et suivie d'un silence obligatoire équivalent à 12 octets. Une trame Ethernet transporte au plus 1 500 octets de paquet (ETH_DATA_LEN 1500) : c'est la MTU (Maximum Transmission Unit) d'Ethernet, qui reviendra à la leçon 6.

Le modèle OSI, et pourquoi on parle encore de « couche 7 »

Vous rencontrerez aussi un modèle à sept couches, le modèle OSI (Open Systems Interconnection), publié par l'ISO en 1983 (norme ISO 7498) :

OSITCP/IP
7ApplicationApplication
6PrésentationApplication
5SessionApplication
4TransportTransport
3RéseauInternet
2Liaison de donnéesLien
1PhysiqueLien (et matériel)

L'histoire de ces deux modèles est celle d'une rivalité. Dans les années 1980, les grands opérateurs de télécommunications et les États soutenaient OSI, conçu en comité, et les protocoles qui allaient avec. Le gouvernement américain imposa même des produits conformes à OSI pour ses administrations à partir d'août 1990. Pendant ce temps, TCP/IP, issu de l'ARPANET et des universités, fonctionnait déjà, était disponible gratuitement dans Unix BSD, et se diffusait. L'historien Andrew Russell rapporte la formule d'un défenseur d'Internet de l'époque, Einar Stefferud : « OSI est un beau rêve, et TCP/IP est en train de le vivre ». Au milieu des années 1990, les protocoles OSI avaient pratiquement disparu.

Le vocabulaire d'OSI, lui, a survécu. Quand on dit qu'un répartiteur de charge travaille « en couche 4 », on veut dire qu'il répartit des connexions TCP sans lire leur contenu ; « en couche 7 », qu'il lit les requêtes HTTP et peut décider selon le chemin ou un en-tête. Un commutateur « de niveau 2 » aiguille des trames selon les adresses MAC ; un routeur « de niveau 3 » aiguille des paquets selon les adresses IP. Ces numéros viennent d'OSI, même quand les protocoles sont ceux de TCP/IP. Les couches 5 et 6 d'OSI n'ont pas d'équivalent distinct dans TCP/IP : leurs fonctions (sessions, chiffrement, encodage) sont prises en charge par l'application ou par des bibliothèques comme TLS, dont on dit, faute de mieux, qu'il est « entre la couche 4 et la couche 7 ».

Note

La RFC 1122 elle-même prévient que le découpage strict en couches est un modèle imparfait : les protocoles de couches différentes interagissent de façons complexes, et de bonnes conceptions « cassent » parfois volontairement la séparation. TCP, par exemple, doit connaître les adresses IP pour calculer sa somme de contrôle. Les couches sont une carte, pas un règlement.

Le principe de bout en bout

Pourquoi IP est-il si simple, sans garantie de livraison, alors que l'on aurait pu construire un réseau qui garantit tout ? La réponse tient dans un article de 1984, End-to-End Arguments in System Design, de Jerome Saltzer, David Reed et David Clark, au MIT. Leur argument, en substance : une fonction ne peut être réalisée complètement et correctement qu'avec la connaissance de l'application, aux extrémités de la communication ; la fournir dans le réseau lui-même est donc impossible, et une version partielle dans le réseau ne se justifie que comme amélioration des performances.

Leur exemple est le transfert de fichier « soigneux ». Pour que le fichier arrive intact sur le disque de B, il faut se protéger d'un disque défaillant en A, d'une erreur de copie dans un tampon mémoire en A ou en B, d'un paquet corrompu ou perdu sur le réseau. Un réseau parfaitement fiable ne protège que du troisième risque. Seule une vérification de bout en bout, par exemple une somme de contrôle du fichier calculée en A et vérifiée en B après écriture, protège de tous. Et si l'on fait cette vérification de toute façon, la fiabilité du réseau devient une simple optimisation.

Ce raisonnement a façonné Internet :

  • le cœur du réseau (IP, les routeurs) reste simple et sans état : il achemine des paquets, au mieux ;
  • l'intelligence (fiabilité, ordre, contrôle de débit, chiffrement) est aux extrémités, dans TCP, TLS et les applications.

C'est ce qui permet d'inventer un nouveau protocole applicatif sans demander la permission à aucun opérateur, et c'est pour cela que l'on représente souvent l'architecture d'Internet comme un sablier : beaucoup de technologies de lien en bas, beaucoup d'applications en haut, et un seul protocole au milieu, IP, que tout le monde parle.

    flowchart TB
  subgraph haut["Applications"]
    direction LR
    h1["HTTP"]
    h2["DNS"]
    h3["SSH"]
    h4["SMTP"]
    h5["..."]
  end
  subgraph milieu["Transport"]
    direction LR
    t1["TCP"]
    t2["UDP"]
    t3["QUIC (sur UDP)"]
  end
  ip["IP"]
  subgraph bas["Liens"]
    direction LR
    b1["Ethernet"]
    b2["Wi-Fi"]
    b3["Fibre, 4G, 5G"]
    b4["..."]
  end
  haut --> milieu --> ip --> bas
  

Pour vous, développeur ou exploitant, la conséquence est concrète : le réseau ne vous doit rien. Un paquet peut se perdre, une connexion peut être coupée au milieu d'une requête, un équipement intermédiaire peut redémarrer. Une application robuste réessaie, vérifie, et rend ses opérations rejouables sans dommage.

Une brève histoire

DateÉtape
1969Premiers nœuds de l'ARPANET, réseau de recherche financé par l'agence ARPA du ministère américain de la Défense
1974Vinton Cerf et Robert Kahn publient la conception d'un protocole pour interconnecter des réseaux différents, l'ancêtre de TCP/IP
Septembre 1981Publication des RFC 791 (IP) et 793 (TCP), éditées par Jon Postel
1er janvier 1983Date fixée par la RFC 801 pour basculer tout l'ARPANET de l'ancien protocole NCP vers TCP/IP. C'est souvent retenu comme la naissance d'Internet
Mai 1983L'ISO publie le modèle de référence OSI (ISO 7498)
Octobre 1989RFC 1122 et 1123, exigences pour les hôtes
Milieu des années 1990Les protocoles OSI sont abandonnés ; TCP/IP est partout
Août 2022RFC 9293, nouvelle spécification de TCP, qui remplace la RFC 793 et intègre quarante ans de corrections

L'IETF et les RFC

Les protocoles d'Internet sont normalisés par l'IETF (Internet Engineering Task Force), une organisation ouverte où participent des individus, pas des pays. Sa devise officieuse vient d'une présentation de David Clark en 1992, citée dans la RFC 7282 : « Nous rejetons les rois, les présidents et les votes. Nous croyons au consensus approximatif et au code qui tourne. »

Ses documents s'appellent des RFC (Request for Comments), un nom modeste hérité de 1969. Quelques clés pour les lire :

  • Une RFC ne change jamais. Une correction donne une nouvelle RFC, qui en « met à jour » (Updates) ou en « remplace » (Obsoletes) une autre. L'en-tête de chaque RFC l'indique. TCP était décrit par la RFC 793 de 1981, il l'est désormais par la RFC 9293 ; une page web qui cite encore la 793 n'est pas fausse, mais elle est ancienne.
  • Toutes les RFC ne sont pas des normes. Le champ Category dit s'il s'agit d'une norme (Standards Track), d'une bonne pratique (Best Current Practice, BCP), d'une information, d'une expérience, ou parfois d'une plaisanterie publiée un 1er avril.
  • Les mots en majuscules ont un sens précis. La BCP 14 (RFC 2119, complétée par la RFC 8174 de mai 2017) définit MUST (obligatoire), SHOULD (recommandé, sauf bonne raison documentée), MAY (facultatif), et leurs négations. La RFC 8174 précise que ces mots n'ont ce sens que lorsqu'ils sont écrits en capitales : un should en minuscules est de l'anglais ordinaire.
  • Le site de référence est rfc-editor.org, qui donne aussi les errata connus de chaque RFC.

En pratique

Nous allons suivre la requête de Camille couche par couche, puis observer une vraie connexion TCP sur votre machine, sans aucun accès au réseau extérieur : tout se passe sur l'interface locale (127.0.0.1). Les sorties ont été produites sur Ubuntu 24.04 avec LC_ALL=C.UTF-8.

Écrire la requête

Préparez un répertoire de travail et un faux point de santé, puis lancez un serveur HTTP minimal, celui de la bibliothèque standard de Python, sur le port 8000 :

$ mkdir -p ~/tcpip && cd ~/tcpip
$ echo '{"etat": "ok"}' > sante
$ python3 -m http.server 8000 --bind 127.0.0.1 &

--bind 127.0.0.1 le fait écouter uniquement sur l'interface locale : il est injoignable depuis une autre machine (leçon 3). Demandez maintenant à curl de montrer exactement ce qu'il envoie :

$ curl -s --trace-ascii trace.txt http://127.0.0.1:8000/sante
{"etat": "ok"}
$ sed -n '/Send header/,/^== Info/p' trace.txt
=> Send header, 82 bytes (0x52)
0000: GET /sante HTTP/1.1
0015: Host: 127.0.0.1:8000
002b: User-Agent: curl/8.5.0
0043: Accept: */*
0050: 
== Info: HTTP 1.0, assume close after body

Voilà la couche application : 82 octets de texte, quatre lignes terminées chacune par un retour chariot et un saut de ligne, et une ligne vide qui marque la fin des en-têtes. Les nombres à gauche sont des positions en hexadécimal (0x15 = 21 : la deuxième ligne commence au 22e octet). Le serveur de Python répond en HTTP/1.0 et ferme la connexion après la réponse, d'où la dernière ligne d'information de curl.

Dérouler l'encapsulation

Sur le chemin réel de Camille, vers 203.0.113.10, le message HTTP est confié à TCP, qui ajoute son en-tête ; puis à IP ; puis au pilote de la carte réseau. Faisons les comptes pour ces 82 octets, avec les en-têtes que Linux utilise par défaut :

CoucheAjouteTaille cumuléeChamp qui désigne la couche du dessus
Application (HTTP)82 octets de requête82
Transport (TCP)32 octets (20 + 12 d'horodatage)114port de destination 80 (ou 8000 sur sig-app-1)
Internet (IPv4)20 octets134protocole 6 (TCP)
Lien (Ethernet)14 octets d'en-tête + 4 de FCS152EtherType 0x0800 (IPv4)
Sur le câble8 de préambule + 12 de silence172

Sur les 172 « octets de temps » occupés sur le câble, 82 seulement sont la requête : moins de la moitié. Pour une requête aussi courte, les enveloppes coûtent plus cher que la lettre. À l'inverse, un segment TCP plein (1 448 octets de données dans 1 500 octets de paquet) occupe 1 538 octets sur le câble, soit 94 % d'efficacité. Retenez l'ordre de grandeur : beaucoup de petits messages coûtent cher, ce qui explique les efforts de HTTP/2 et HTTP/3 pour regrouper les échanges, et l'intérêt de grouper les écritures dans une application.

Tip

L'option d'horodatage TCP est active quand /proc/sys/net/ipv4/tcp_timestamps vaut 1, la valeur par défaut. Vérifiez-le avec cat /proc/sys/net/ipv4/tcp_timestamps : c'est la raison pour laquelle, sur un lien Ethernet, les segments TCP de Linux transportent 1 448 octets de données et non 1 460.

Observer une connexion vivante

Une connexion TCP ne dure souvent que quelques millisecondes : trop peu pour l'observer. Ouvrons-en une à la main avec Python, gardons-la ouverte quelques secondes avant d'envoyer la requête, et regardons-la pendant ce temps avec ss (socket statistics), l'outil d'iproute2 qui remplace l'ancien netstat :

$ python3 - <<'EOF' &
import socket, time
s = socket.create_connection(("127.0.0.1", 8000))
time.sleep(3)
s.sendall(b"GET /sante HTTP/1.1\r\nHost: 127.0.0.1:8000\r\n\r\n")
print(s.recv(4096).decode())
EOF
$ ss -tnp 'sport = :8000 or dport = :8000'
State Recv-Q Send-Q Local Address:Port  Peer Address:Port Process
ESTAB 0      0          127.0.0.1:8000     127.0.0.1:50196 users:(("python3",pid=663506,fd=4))
ESTAB 0      0          127.0.0.1:50196    127.0.0.1:8000  users:(("python3",pid=664221,fd=3))

Les options : -t pour TCP seulement, -n pour afficher les ports en chiffres plutôt qu'en noms de services, -p pour afficher le processus (seulement pour vos propres processus, sauf en root). Le filtre entre apostrophes garde les sockets dont le port source ou destination est 8000.

Lisez les deux lignes : une connexion TCP a deux extrémités, et comme le client et le serveur sont sur la même machine, on voit les deux. Côté serveur, la socket locale est 127.0.0.1:8000 et le pair 127.0.0.1:50196 ; côté client, c'est l'inverse. Le port 50196 n'a été choisi par personne : le noyau l'a pris dans la plage des ports éphémères, 32768 à 60999 par défaut (/proc/sys/net/ipv4/ip_local_port_range, leçon 9). Le couple d'adresses et de ports des deux extrémités, avec le protocole, identifie la connexion de façon unique : c'est le quintuplet (protocole, adresse source, port source, adresse destination, port destination), que l'on retrouvera dans le NAT (leçon 7) et dans les pare-feu à état.

Le serveur a d'ailleurs deux sockets : celle qui écoute (état LISTEN, descripteur 3), et celle de cette connexion (descripteur 4), créée par le noyau quand la connexion a été acceptée. ss -ltn montre les premières.

Ce que le noyau sait

ss lit ses informations dans le noyau. La vieille interface texte, toujours présente, est /proc/net/tcp. Pendant que la connexion était ouverte :

$ head -1 /proc/net/tcp
  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
$ grep 1F40 /proc/net/tcp
  14: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000  1000        0 18357213 1 0000000000000000 100 0 0 10 0
  55: 0100007F:1F40 0100007F:C414 01 00000000:00000000 00:00000000 00000000  1000        0 18370755 1 0000000000000000 20 0 0 10 -1
  60: 0100007F:C414 0100007F:1F40 01 00000000:00000000 00:00000000 00000000  1000        0 18372748 1 0000000000000000 20 0 0 10 -1
  61: 0100007F:DD42 0100007F:1F40 06 00000000:00000000 03:00000B06 00000000     0        0 0 3 0000000000000000

Tout est en hexadécimal :

  • 1F40 vaut 8000, C414 vaut 50196 : on retrouve nos ports.
  • 0100007F est l'adresse 127.0.0.1, écrite à l'envers. Le noyau affiche l'entier de 32 bits tel qu'il est rangé en mémoire sur un processeur x86, dont l'ordre des octets (little-endian) est l'inverse de l'ordre du réseau (big-endian, l'octet de poids fort d'abord). 7F 00 00 01 lu dans l'ordre du réseau, c'est 127.0.0.1. Ce genre de détail est la raison d'être des fonctions htons et ntohl en C.
  • La colonne st est l'état : 0A pour LISTEN, 01 pour ESTABLISHED, 06 pour TIME_WAIT. La dernière ligne est une connexion précédente vers le port 8000, déjà fermée, que le noyau garde un moment en TIME_WAIT (leçon 10).
  • uid est l'utilisateur propriétaire de la socket, inode l'identifiant de la socket, que l'on retrouve dans /proc/<pid>/fd/.

Vous pouvez vérifier les conversions avec Python :

$ python3 -c "print(0x1F40, 0xC414, 0x0100007F)"
8000 50196 16777343

Arrêtez le serveur avec kill %1 (ou fg puis Ctrl+C).

Sous le capot

Où vit la pile. Dans Linux, comme dans la plupart des systèmes d'exploitation, les couches transport, internet et une partie de la couche lien sont dans le noyau. L'application ne fabrique pas d'en-tête TCP ni de paquet IP : elle demande au noyau une socket (appel système socket(2)), lui demande de se connecter (connect(2)) ou d'écouter (bind(2), listen(2), accept(2)), puis lit et écrit des octets (read, write, send, recv). Le noyau découpe, ajoute les en-têtes, retransmet, réordonne. La couche lien est partagée entre le noyau et le pilote de la carte, qui programme le matériel ; la carte elle-même calcule souvent la somme de contrôle Ethernet et, sur les cartes récentes, une partie des sommes de contrôle IP et TCP (offload).

    flowchart TB
  app["Application (curl, Gunicorn)<br/>espace utilisateur"]
  sock["Socket : socket(), connect(), send(), recv()"]
  tcp["TCP / UDP"]
  ip["IP : routage, fragmentation"]
  neigh["Voisinage : ARP, NDP"]
  drv["Pilote de la carte"]
  nic["Carte réseau (matériel)"]
  app --> sock
  subgraph noyau["Noyau Linux"]
    sock --> tcp --> ip --> neigh --> drv
  end
  drv --> nic
  

Ce découpage a deux conséquences pratiques. D'abord, une application ne voit jamais les paquets : pour les observer, il faut un outil qui demande au noyau une copie de ce qui passe sur l'interface (tcpdump, leçon 12), ce qui exige des privilèges. Ensuite, beaucoup de réglages du réseau sont des réglages du noyau, visibles et modifiables sous /proc/sys/net/ : la plage de ports éphémères, les délais d'ARP, le transfert de paquets entre interfaces.

Pourquoi le serveur a deux sockets. Une socket en écoute ne transporte aucune donnée : elle sert de file d'attente pour les connexions entrantes. Quand un client se connecte, le noyau réalise la poignée de main TCP tout seul, puis range la connexion établie dans la file. accept(2) la retire de la file et rend un nouveau descripteur. C'est pour cela que l'on voit un LISTEN et un ESTAB côte à côte, et que la colonne Send-Q d'une socket en écoute indique en fait la taille maximale de cette file (5 pour le petit serveur de Python, avec ss -ltn).

L'interface locale n'est pas un raccourci magique. Le trafic vers 127.0.0.1 traverse bien la pile TCP et IP du noyau, avec ses en-têtes ; seule la couche lien est simulée par l'interface lo, qui « renvoie » chaque paquet à la machine elle-même. C'est ce qui rend l'expérience de cette leçon représentative.

Pièges courants

Confondre les erreurs des différentes couches. Could not resolve host : le nom n'a pas été traduit en adresse (DNS, application). No route to host ou Network is unreachable : la couche internet ne sait pas où envoyer le paquet (routage, leçon 5). Connection timed out : les paquets partent, mais rien ne revient (filtrage silencieux, machine éteinte, route de retour absente). Connection refused : la machine a répondu, mais aucun programme n'écoute sur ce port (transport). Rien que cette lecture divise le temps de diagnostic.

Croire que le réseau est fiable. C'est la première des « illusions de l'informatique répartie » souvent attribuées à Peter Deutsch. Le principe de bout en bout le dit autrement : la garantie est aux extrémités. Un appel réseau dans une application doit avoir un délai d'attente, une politique de nouvelle tentative et une réponse prévue à l'échec.

Lire « couche 4 » et « couche 7 » comme des notions vagues. Un répartiteur L4 ne voit que des connexions TCP : il ne peut pas router selon le chemin /sante, ni ajouter un en-tête X-Forwarded-For, ni terminer TLS. Un répartiteur L7 le peut, mais il lit vos requêtes, donc il doit déchiffrer le trafic. Le choix a des conséquences sur la sécurité et les fonctionnalités, comme on l'a vu avec le répartiteur de Scaleway dans le cours Le cloud : les fondamentaux.

Citer une RFC remplacée. Avant d'appuyer un argument sur une RFC, regardez son en-tête : si elle est Obsoleted by une autre, lisez la plus récente. Et vérifiez sa catégorie : une RFC Informational n'oblige personne.

Oublier les octets d'en-tête dans un calcul de débit. Un lien à 1 Gbit/s ne transporte pas 125 Mo/s de données utiles : préambules, en-têtes et accusés de réception en prennent une part, d'autant plus grande que les messages sont petits.

Sécurité

Le modèle en couches éclaire plusieurs réalités de la sécurité :

  • Chaque couche a ses attaques. Usurpation d'adresse MAC ou empoisonnement ARP en couche 2 (leçon 2), usurpation d'adresse IP en couche 3, inondation de demandes de connexion TCP en couche 4, injections et contournements d'authentification en couche 7. Un pare-feu qui ne filtre que les adresses et les ports (couches 3 et 4) ne voit rien d'une attaque dans une requête HTTP valide.
  • Les en-têtes ne sont pas authentifiés. Ni IP, ni TCP, ni Ethernet ne prouvent l'identité de l'expéditeur : une adresse source s'écrit comme on veut, et ce sont les équipements du réseau qui, au mieux, filtrent les incohérences. L'authentification et la confidentialité relèvent d'un protocole conçu pour cela, de bout en bout : TLS (cours TLS et PKI), SSH, ou un tunnel chiffré.
  • Le principe de bout en bout s'applique au chiffrement. Un réseau « sûr » (un réseau privé d'entreprise, un réseau privé virtuel chez un fournisseur de cloud) ne remplace pas le chiffrement entre les applications : il protège de certaines menaces, pas d'un équipement compromis au milieu ni d'une erreur de configuration. C'est l'idée qui sous-tend le zero trust.
  • Les métadonnées restent visibles. Même avec TLS, les en-têtes IP et TCP circulent en clair : qui parle à qui, quand, combien. Le chiffrement protège le contenu, pas le fait de communiquer.

En production

  • Diagnostiquez de bas en haut, ou de haut en bas, mais par couche. Le lien est-il actif ? L'adresse est-elle configurée ? La route existe-t-elle ? Le port répond-il ? L'application répond-elle correctement ? La leçon 12 transforme cette idée en méthode.
  • Les équipements du milieu ne sont pas tous des routeurs simples. Pare-feu, traducteurs d'adresses, répartiteurs de charge et proxys lisent ou modifient des couches qui, en théorie, ne les regardent pas. Ces middleboxes expliquent bien des comportements surprenants, comme une connexion inactive coupée au bout de quelques minutes par un pare-feu à état qui a oublié son existence.
  • Les petites requêtes coûtent cher à grande échelle. Une API qui fait dix appels de cent octets au lieu d'un appel de mille paie dix fois les en-têtes, les allers-retours et souvent les poignées de main.
  • Chez Lyneko, l'application Signalements est exposée par un répartiteur de charge HTTP (couche 7) qui termine TLS et transmet les requêtes en clair aux instances dans un réseau privé, comme dans le cours Le cloud : les fondamentaux : un choix de couche qui détermine ce que l'on peut journaliser, router et protéger.

Exercices

1. Quelle couche ? (niveau 100). Pour chacune des situations suivantes, dites quelle couche est en cause et quelle adresse ou quel identifiant est concerné : (a) curl affiche Could not resolve host: api.signalements.example ; (b) curl affiche Connection refused vers 203.0.113.10:80 ; (c) le voyant de la carte réseau de sig-app-1 est éteint ; (d) un répartiteur de charge renvoie la requête /admin vers un autre groupe de serveurs que /sante.

Solution

(a) Application : le nom n'a pas pu être traduit en adresse IP, le DNS est en cause (ou le nom est faux). (b) Transport : la machine 203.0.113.10 a répondu, mais rien n'écoute sur le port 80 ; le paquet est bien arrivé, la couche internet fonctionne. (c) Lien, et même en dessous (physique en OSI) : rien ne peut passer, quelle que soit l'adresse. (d) Application (couche 7 en vocabulaire OSI) : seul un équipement qui lit la requête HTTP peut distinguer /admin de /sante.

2. Compter les octets (niveau 100). Une application envoie un message de 1 000 octets par UDP sur IPv4 et Ethernet. Combien d'octets fait la trame (FCS comprise) ? Combien d'octets de temps occupe-t-elle sur le câble ? Même question avec TCP sous Linux (horodatage actif).

Solution

UDP : 1 000 + 8 (UDP) + 20 (IPv4) + 14 (Ethernet) + 4 (FCS) = 1 046 octets de trame, et 1 046 + 8 + 12 = 1 066 octets de temps sur le câble. TCP : 1 000 + 32 + 20 + 14 + 4 = 1 070 octets de trame, 1 090 sur le câble. Dans les deux cas, le paquet IP (1 028 ou 1 052 octets) tient sous la MTU de 1 500 : pas de découpage.

3. Lire /proc/net/tcp (niveau 100). Une ligne de /proc/net/tcp contient 0B14A8C0:0016 6401A8C0:D2F0 01. Traduisez-la : adresses, ports, état.

Solution

Adresse locale 0B14A8C0 : les octets à l'envers donnent C0 A8 14 0B, soit 192.168.20.11. Port local 0x0016 = 22 (SSH). Adresse distante 6401A8C0 donne C0 A8 01 64, soit 192.168.1.100. Port distant 0xD2F0 = 54000, un port éphémère. État 01 : ESTABLISHED. C'est une session SSH établie depuis 192.168.1.100 vers cette machine. Vérification : python3 -c "import ipaddress; print(ipaddress.ip_address(bytes.fromhex('0B14A8C0')[::-1]))".

4. Bout en bout (niveau 100). L'équipe propose de supprimer la vérification de la somme SHA-256 des sauvegardes de la base de Signalements, au motif que le transfert vers le stockage objet se fait en HTTPS, « donc sans erreur possible ». Que répondez-vous, avec l'argument de Saltzer, Reed et Clark ?

Solution

HTTPS protège le transfert sur le réseau (intégrité et confidentialité entre les deux extrémités de la connexion TLS), mais pas le reste du chemin : la lecture du fichier sur le disque source, une erreur dans le programme de sauvegarde, une copie en mémoire, l'écriture côté stockage, une corruption ultérieure. Seule une vérification entre les vraies extrémités, la somme calculée sur la sauvegarde d'origine et vérifiée sur le fichier relu après dépôt, couvre tous ces risques. TLS est une optimisation utile, pas une preuve que la sauvegarde est intacte. On garde la vérification, et on la complète par un test de restauration.

Récapitulatif

  • Le modèle TCP/IP découpe la communication en quatre couches (RFC 1122) : lien (adresse MAC, trame), internet (adresse IP, paquet), transport (port, segment TCP ou datagramme UDP), application (message).
  • Chaque couche encapsule les données de la couche du dessus dans son en-tête, et indique qui doit les traiter ensuite : EtherType, numéro de protocole, port.
  • Tailles à retenir : Ethernet 14 + 4, IPv4 20, IPv6 40, TCP 20 (32 sous Linux avec horodatage), UDP 8 ; MTU Ethernet 1 500 octets.
  • Le modèle OSI à sept couches a perdu la bataille des protocoles mais gagné celle du vocabulaire : « couche 2 », « couche 4 », « couche 7 ».
  • Le principe de bout en bout garde le cœur du réseau simple et place la fiabilité, l'ordre et le chiffrement aux extrémités : le réseau ne garantit rien, l'application doit le prévoir.
  • Sous Linux, la pile réseau est dans le noyau ; les applications passent par des sockets ; ss et /proc/net/tcp montrent l'état des connexions.
  • Les RFC font foi : regarder si elles sont remplacées, leur catégorie, et lire MUST et SHOULD en capitales selon la BCP 14.

Pour aller plus loin

  • La RFC 1122, sections 1.1 et 1.3 : une vingtaine de pages qui posent l'architecture, toujours d'actualité.
  • L'article de Saltzer, Reed et Clark, douze pages, l'un des textes les plus cités de l'histoire des réseaux.
  • TCP/IP Illustrated, Volume 1, de Kevin Fall et W. Richard Stevens, chapitre 1, pour une présentation de l'architecture par ceux qui l'ont le mieux expliquée.
  • La leçon suivante, qui descend à la couche la plus basse que l'on manipule au quotidien : Ethernet et la traduction des adresses IP en adresses MAC.
Voir ma constellation →

Sources