Aller au contenu
La traduction d'adresses (NAT)

La traduction d'adresses (NAT)

200 Pratiquer ⏱ 1 h 15 reseaulinuxipv4nftables

À la fin, vous saurez

  • Expliquer pourquoi la traduction d'adresses existe, et ce qui distingue NAT de base, NAPT et masquerade
  • Suivre les adresses et les ports d'une connexion à travers une box, puis à travers un répartiteur de charge
  • Décrire le rôle de la table de suivi de connexion, ses états et ses délais d'expiration, et reconnaître une table pleine
  • Configurer, en lecture, une redirection de port et un NAT source avec nftables
  • Identifier les conséquences du NAT : connexions entrantes impossibles, protocoles qui transportent des adresses, NAT de l'opérateur, épuisement des ports
  • Distinguer l'effet de filtrage du NAT d'un véritable pare-feu

Prérequis

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

Pourquoi

Faites l'expérience chez vous : votre ordinateur portable, votre téléphone, la télévision et la console de jeu sont tous connectés à Internet en même temps. Ils ont chacun une adresse IPv4, du genre 192.168.1.23. Pourtant, le site que vous consultez ne voit jamais ces adresses : il voit une seule adresse, celle que votre opérateur a attribuée à votre box. Quatre machines, une adresse publique.

C'est la traduction d'adresses réseau (Network Address Translation, NAT). Sans elle, l'Internet en IPv4 tel que nous le connaissons n'existerait plus depuis longtemps. Une adresse IPv4 fait 32 bits, soit un peu plus de quatre milliards de valeurs, dont une bonne part réservée à des usages particuliers. L'IANA a distribué ses derniers blocs aux registres régionaux en 2011, et le registre européen, le RIPE NCC, a fait sa dernière attribution ordinaire le 25 novembre 2019 : il ne fait plus depuis que redistribuer les adresses rendues, sur liste d'attente. Le NAT est ce qui a permis de continuer à connecter des milliards d'appareils malgré cette pénurie, en attendant IPv6 (leçon 8).

Le NAT est partout sur le trajet de la requête GET /sante que suit ce cours. La box de l'utilisatrice traduit son adresse privée en adresse publique. Le serveur sig-app-1 n'a pas d'adresse publique du tout : quand il télécharge une mise à jour, c'est la passerelle publique du réseau privé qui traduit pour lui. Docker, quand il publie le port d'un conteneur, écrit des règles de NAT. Et l'opérateur mobile de l'utilisatrice ajoute parfois sa propre couche de traduction.

Sans comprendre le NAT, on ne comprend pas pourquoi un serveur chez soi est injoignable depuis l'extérieur, pourquoi les journaux d'une application affichent toujours la même adresse cliente, pourquoi une machine refuse soudain toute nouvelle connexion alors que le processeur dort, ni pourquoi « on est derrière un NAT, donc on est protégé » est une idée dangereuse.

Les concepts

Des adresses qui ne sortent pas : la RFC 1918

En 1996, la RFC 1918 met de côté trois blocs d'adresses pour les réseaux privés :

BlocNotation CIDRNombre d'adressesUsage typique
10.0.0.0 à 10.255.255.25510.0.0.0/8environ 16,8 millionsgrandes entreprises, réseaux cloud
172.16.0.0 à 172.31.255.255172.16.0.0/12environ 1 millionréseaux cloud, Docker
192.168.0.0 à 192.168.255.255192.168.0.0/1665 536box domestiques, petits bureaux

N'importe qui peut les utiliser sans rien demander à personne, et des milliers de réseaux utilisent les mêmes. Le prix de cette liberté : ces adresses ne sont pas routables sur Internet. La RFC demande que les informations de routage sur ces réseaux ne soient pas propagées entre organisations, et que les opérateurs filtrent les paquets qui les portent. Un paquet dont l'adresse source est 192.168.1.23 peut partir vers Internet, mais la réponse n'aurait aucun chemin pour revenir : des millions de machines portent cette adresse.

Le réseau privé de Signalements chez Scaleway, 172.16.20.0/22, et le réseau domestique de l'utilisatrice, 192.168.1.0/24, sont deux réseaux RFC 1918. Pour communiquer avec le reste du monde, il faut un traducteur à la frontière.

NAT de base, NAPT, masquerade

La RFC 3022 de 2001 décrit ce qu'elle appelle le NAT « traditionnel », sous deux formes :

  • Le NAT de base (Basic NAT) remplace une adresse par une autre, sans toucher aux ports. Il faut alors une adresse publique par machine qui communique en même temps : utile pour renuméroter, inutile contre la pénurie.
  • La traduction d'adresses et de ports (Network Address Port Translation, NAPT) remplace l'adresse et le port source. Des milliers de connexions venues de machines différentes peuvent ainsi partager une seule adresse publique, puisque chacune reçoit un port distinct côté public. C'est ce que fait votre box, et c'est ce que tout le monde appelle « NAT » au quotidien.

Le noyau Linux distingue deux variantes du NAT source :

  • SNAT (source NAT) : on indique l'adresse publique à utiliser. Convient à un routeur dont l'adresse publique est fixe.
  • Masquerade : le noyau prend automatiquement l'adresse de l'interface de sortie au moment du paquet. Convient quand l'adresse publique change (box domestique, adresse attribuée par DHCP), au prix d'un léger surcoût.

La table de traduction

Le principe tient en une table. Quand un paquet sort, le traducteur note la correspondance entre l'adresse et le port d'origine et l'adresse et le port qu'il leur substitue. Quand une réponse revient sur le port public, il consulte la table et fait la traduction inverse.

Suivons l'utilisatrice, sur son portable 192.168.1.23, qui envoie GET /sante au répartiteur de charge de Signalements, 203.0.113.10, port 443 :

    sequenceDiagram
  participant P as Portable<br/>192.168.1.23
  participant B as Box<br/>192.168.1.1 / 198.51.100.7
  participant L as Répartiteur<br/>203.0.113.10
  P->>B: src 192.168.1.23:51514<br/>dst 203.0.113.10:443
  Note over B: note la correspondance<br/>51514 ↔ 40312
  B->>L: src 198.51.100.7:40312<br/>dst 203.0.113.10:443
  L->>B: src 203.0.113.10:443<br/>dst 198.51.100.7:40312
  Note over B: retrouve 192.168.1.23:51514
  B->>P: src 203.0.113.10:443<br/>dst 192.168.1.23:51514
  

Les numéros de port sont des exemples : le port source du portable est choisi par son noyau dans sa plage éphémère (leçon 9), le port public par la box. La box conserve souvent le port d'origine quand il est libre, et en choisit un autre sinon.

Trois propriétés découlent de ce mécanisme :

  1. Le serveur ne voit jamais l'adresse privée. Pour le répartiteur, la cliente s'appelle 198.51.100.7:40312. Tous les appareils du foyer partagent cette adresse.
  2. Seul le premier paquet sortant crée la correspondance. Un paquet qui arrive de l'extérieur sur un port pour lequel il n'existe pas d'entrée n'a aucune destination : la box ne sait pas à quelle machine le donner. Il est jeté.
  3. La correspondance a une durée de vie. Une entrée qui ne voit passer aucun paquet pendant un certain temps est effacée. Une connexion qui se tait trop longtemps est alors coupée, sans que ni le client ni le serveur n'en soient prévenus.

Le comportement attendu d'un NAT

Pendant des années, chaque fabricant a implémenté le NAT à sa façon, et les applications ont dû deviner. L'IETF a fini par publier des exigences de comportement : la RFC 4787 pour UDP (2007) et la RFC 5382 pour TCP (2008). Deux notions y sont centrales.

Le comportement de correspondance (mapping) dit si le NAT réutilise le même port public pour toutes les destinations :

  • indépendant de la destination (endpoint-independent mapping) : 192.168.1.23:51514 sort toujours en 198.51.100.7:40312, quel que soit le serveur visé ;
  • dépendant de l'adresse de destination, ou de l'adresse et du port : un nouveau port public par destination.

Les deux RFC exigent le premier : c'est lui qui permet aux applications pair à pair de découvrir leur adresse publique et de la communiquer à un correspondant.

Le comportement de filtrage dit qui a le droit d'envoyer des paquets vers un port public déjà attribué : n'importe qui (endpoint-independent filtering), seulement les adresses déjà contactées, ou seulement les couples adresse et port déjà contactés.

Les RFC fixent aussi des délais minimaux :

SituationDélai minimalSource
Correspondance UDP inactive2 minutes (5 minutes ou plus recommandées)RFC 4787, REQ-5
Connexion TCP établie inactive2 heures et 4 minutesRFC 5382, REQ-5
Connexion TCP en cours d'ouverture ou de fermeture4 minutesRFC 5382, REQ-5

Les deux heures et quatre minutes ne sont pas un hasard : elles couvrent la période par défaut des messages de maintien de connexion de TCP (deux heures), plus une marge. Beaucoup d'équipements, et surtout beaucoup de services cloud, ne respectent pas ces minimums, ce qui est la source de bien des coupures mystérieuses (voir En production).

Le suivi de connexion

Sous Linux, la table de traduction n'est pas une structure à part : elle est une propriété du suivi de connexion (connection tracking, conntrack), le sous-système de Netfilter qui tient à jour, pour chaque flux qui traverse la machine, une entrée décrivant ses deux sens. La documentation du noyau le précise : chaque entrée est ajoutée deux fois à la table de hachage, une fois pour le sens d'origine et une fois pour le sens de la réponse. C'est ce qui permet de reconnaître un paquet de réponse et de lui appliquer la traduction inverse.

Chaque paquet reçoit un état au regard de ce suivi. Le wiki de nftables les définit ainsi :

ÉtatSignification
newdes paquets n'ont été vus que dans un sens, et au moins l'un d'eux ouvre valablement un flux (un SYN pour TCP)
establisheddes paquets valides ont circulé dans les deux sens ; pour TCP, la poignée de main est terminée
relatedun flux ouvert à la suite d'un autre, comme prévu par le protocole (canal de données FTP, message d'erreur ICMP lié à une connexion)
invalidun paquet qui ne correspond à aucun comportement attendu
untrackedun paquet volontairement exclu du suivi

Le NAT de Linux ne s'applique qu'au premier paquet d'un flux : c'est lui qui traverse les règles de la table nat. La décision est ensuite enregistrée dans l'entrée de suivi, et tous les paquets suivants, dans les deux sens, sont traduits directement à partir de cette entrée, sans repasser par les règles.

La redirection de port

Le NAT source permet de sortir. Pour entrer depuis Internet vers une machine privée, il faut l'inverse : un NAT de destination (destination NAT, DNAT), aussi appelé redirection de port (port forwarding). On déclare à l'avance qu'un paquet qui arrive sur tel port de l'adresse publique doit être réécrit vers telle adresse et tel port privés.

On en rencontre trois formes dans ce cours :

  • La box domestique : la règle « port 8022 de l'adresse publique vers 192.168.1.40:22 » rend une machine du foyer joignable de l'extérieur.
  • Docker : docker run -p 8080:8000 écrit une règle de DNAT qui envoie le port 8080 de l'hôte vers le port 8000 du conteneur. Le cours Docker : les fondamentaux montre ces règles et le piège qu'elles tendent aux pare-feu de l'hôte (glossaire : port publié).
  • La passerelle publique de Scaleway : son NAT statique associe un port de l'adresse publique de la passerelle à une adresse et un port du réseau privé, quand on veut exposer un service sans répartiteur.

Le NAT de l'opérateur (CGNAT)

Votre box fait du NAT. Mais si votre opérateur n'a pas assez d'adresses publiques pour en donner une à chaque box, il en ajoute un second, dans son propre réseau : le NAT de niveau opérateur (Carrier-Grade NAT, CGNAT ou CGN). La box reçoit alors une adresse qui n'est pas publique non plus, et des centaines d'abonnés partagent une même adresse publique, chacun avec une tranche de ports.

Pour ne pas réutiliser les adresses RFC 1918, que les abonnés emploient déjà chez eux, la RFC 6598 a réservé en 2012 un bloc dédié : 100.64.0.0/10, l'« espace d'adressage partagé ». La RFC précise qu'il se distingue de l'espace privé, puisqu'il est destiné aux réseaux d'opérateurs, et que ses adresses ne doivent jamais franchir la frontière d'un opérateur. Si ip addr sur votre box ou votre téléphone affiche une adresse en 100.64.x.x à 100.127.x.x, vous êtes derrière un CGNAT.

La RFC 6888 (2013) fixe les exigences d'un CGNAT. Retenons-en trois, parce qu'elles ont des conséquences directes pour une application :

  • Une même adresse publique par abonné pendant toute sa session (comportement dit paired, exigé par défaut) : sans cela, un site qui associe une session à une adresse verrait son client changer d'adresse au milieu de la navigation.
  • Des limites par abonné sur le nombre de ports : un abonné ne doit pas pouvoir épuiser les ports des autres.
  • La journalisation : pour pouvoir répondre à une réquisition judiciaire, l'opérateur doit savoir quel abonné utilisait quelle adresse et quels ports à quel instant. La RFC recommande de ne pas journaliser les destinations, pour protéger la vie privée.

Pour vous, côté serveur, cela signifie : une adresse IP n'identifie pas une personne. Des centaines d'utilisateurs peuvent arriver de la même adresse, et bloquer une adresse peut bloquer tout un quartier. Pour identifier précisément un client derrière un CGNAT, il faut l'adresse et le port source, avec l'heure exacte : c'est pourquoi on recommande de journaliser le port source des connexions entrantes.

Ce que le NAT casse

Le modèle d'origine d'Internet est dit de bout en bout : chaque machine a une adresse unique, et n'importe quelle machine peut en joindre une autre. Le NAT rompt ce modèle de plusieurs façons :

  • Les connexions entrantes sont impossibles sans redirection configurée à l'avance. Un serveur de jeu, un serveur de visioconférence pair à pair ou une caméra derrière une box ne peuvent pas être joints directement.
  • Les protocoles qui transportent des adresses dans leurs données sont brisés, parce que le NAT réécrit les en-têtes, pas le contenu. Le FTP en mode actif envoie l'adresse et le port où le client attend la connexion de données : une adresse privée, inutilisable par le serveur. SIP, utilisé pour la téléphonie sur IP, annonce de la même façon les adresses de ses flux audio. Pour s'en sortir, les NAT embarquent des passerelles applicatives (Application Level Gateway, ALG), qui lisent et réécrivent le contenu de ces protocoles. La RFC 3022 note qu'une ALG cesse de fonctionner dès que le contenu est chiffré.
  • Les applications pair à pair doivent ruser : un serveur tiers dit à chaque pair sous quelle adresse publique il apparaît (protocole STUN), et les deux pairs envoient en même temps des paquets l'un vers l'autre pour ouvrir chacun une correspondance dans leur NAT. Quand cela échoue, un serveur relais (TURN) fait transiter tout le trafic. C'est ce que font les applications de visioconférence dans votre navigateur.

En pratique

Les commandes de cette section qui modifient les règles de pare-feu exigent les droits de root, et ne doivent être essayées que sur une machine virtuelle jetable. Elles sont expliquées sans sortie ; les seules sorties montrées proviennent de commandes en lecture seule, capturées sur une machine Ubuntu 24.04.

Observer la table de suivi de connexion

La taille de la table et le nombre d'entrées en cours se lisent sans privilège, avec sysctl, sur une machine où le module de suivi de connexion est chargé (c'est le cas dès que Docker ou une règle de pare-feu à état est présent) :

$ sysctl net.netfilter.nf_conntrack_max net.netfilter.nf_conntrack_count
net.netfilter.nf_conntrack_max = 262144
net.netfilter.nf_conntrack_count = 375
  • nf_conntrack_max est le nombre maximal d'entrées. D'après la documentation du noyau, il vaut par défaut le nombre de cases de la table de hachage, lui-même calculé à partir de la mémoire de la machine (la mémoire divisée par 16 384), dans la limite de 262 144. La machine de test, avec 32 Gio de mémoire, atteint ce plafond.
  • nf_conntrack_count est le nombre d'entrées présentes à cet instant.

Les délais d'expiration se lisent de la même façon :

$ sysctl net.netfilter.nf_conntrack_tcp_timeout_established \
    net.netfilter.nf_conntrack_udp_timeout net.netfilter.nf_conntrack_udp_timeout_stream
net.netfilter.nf_conntrack_tcp_timeout_established = 432000
net.netfilter.nf_conntrack_udp_timeout = 30
net.netfilter.nf_conntrack_udp_timeout_stream = 120

Une connexion TCP établie et silencieuse est gardée cinq jours (432 000 secondes) ; un échange UDP isolé, trente secondes ; un flux UDP dans lequel des paquets ont circulé dans les deux sens, deux minutes. Ce sont les valeurs par défaut documentées par le noyau.

Pour voir les entrées elles-mêmes, il faut les droits de root et l'outil conntrack (paquet du même nom) :

$ sudo conntrack -L -p tcp --dport 443

Chaque ligne décrit un flux avec ses deux sens : l'adresse et le port d'origine, puis ce qui est attendu en retour. Quand une traduction a eu lieu, les deux sens ne sont pas symétriques : le sens retour porte l'adresse publique, ce qui permet de lire directement la correspondance.

Faire d'une machine Linux un routeur NAT

Imaginons une machine virtuelle avec deux interfaces : ens2 vers Internet, ens3 vers un réseau privé 10.20.0.0/24. Pour que les machines du réseau privé sortent par elle, il faut deux choses.

D'abord, autoriser le noyau à faire passer des paquets d'une interface à l'autre, ce qu'il refuse par défaut :

$ sudo sysctl -w net.ipv4.ip_forward=1

La documentation du noyau précise que ce réglage vaut 0 par défaut et qu'il est particulier : le changer remet à leur valeur par défaut d'autres réglages des interfaces, selon que la machine se comporte en hôte ou en routeur. Pour le rendre permanent, on l'écrit dans un fichier de /etc/sysctl.d/.

Ensuite, déclarer le NAT avec nftables, dans un fichier /etc/nftables.conf par exemple :

table ip nat {
  chain prerouting {
    type nat hook prerouting priority -100; policy accept;
    # Redirection de port : le port 8022 de l'adresse publique
    # mène au SSH d'une machine du réseau privé.
    iifname "ens2" tcp dport 8022 dnat to 10.20.0.40:22
  }
  chain postrouting {
    type nat hook postrouting priority 100; policy accept;
    # NAT source : tout ce qui vient du réseau privé et sort par ens2
    # prend l'adresse de ens2.
    ip saddr 10.20.0.0/24 oifname "ens2" masquerade
  }
}

Chaque élément a sa raison :

  • table ip nat : une table pour la famille IPv4. Le nom nat est une convention ; c'est le type des chaînes qui compte.
  • type nat hook prerouting priority -100 : une chaîne de type NAT accrochée au point prerouting, c'est-à-dire avant la décision de routage. C'est là que doit se faire le DNAT, puisque changer la destination change la route. La priorité -100 est celle qu'utilise le wiki de nftables pour ce point.
  • type nat hook postrouting priority 100 : le NAT source se fait après la décision de routage, juste avant que le paquet ne sorte, quand on connaît l'interface de sortie.
  • masquerade : prendre l'adresse de l'interface de sortie. Avec une adresse publique fixe, on écrirait snat to 198.51.100.7.
  • oifname "ens2" et ip saddr 10.20.0.0/24 : ne traduire que ce qui doit l'être. Une règle masquerade sans condition traduirait aussi le trafic entre deux réseaux privés, ce qui masque les adresses réelles dans les journaux.

Le fichier se charge avec sudo nft -f /etc/nftables.conf, et sudo nft list ruleset affiche les règles actives. Ce jeu de règles ne filtre rien : il traduit. Le filtrage, qui décide de ce qui a le droit de passer, s'écrit dans une chaîne de type filter au point forward, et fait l'objet du cours Le réseau sous Linux : nftables, namespaces, bridges.

Suivre GET /sante de bout en bout

Reprenons maintenant la requête complète, du portable jusqu'au serveur, et regardons ce que chaque équipement fait aux adresses et aux ports.

TronçonAdresse et port sourceAdresse et port destinationQui a réécrit
Portable vers box192.168.1.23:51514203.0.113.10:443personne
Box vers Internet198.51.100.7:40312203.0.113.10:443la box (NAPT)
Répartiteur vers sig-app-1172.16.20.5:39876172.16.20.11:8000aucune traduction : une nouvelle connexion

La dernière ligne demande une explication. Le répartiteur de charge du cours (cours cloud, leçon 6) fonctionne en HTTP : il termine la connexion TCP et TLS de la cliente, lit la requête, puis ouvre sa propre connexion vers une instance, depuis son adresse dans le réseau privé (ici 172.16.20.5, une valeur d'exemple). Ce n'est pas du NAT au sens strict : c'est un mandataire (proxy). Mais le résultat, vu de sig-app-1, est le même : l'adresse de la cliente a disparu. Gunicorn voit toutes les requêtes arriver de 172.16.20.5.

Pour retrouver l'adresse réelle, le répartiteur ajoute un en-tête HTTP X-Forwarded-For: 198.51.100.7. Ce n'est pas l'adresse du portable, qui n'a jamais quitté le foyer, mais celle de la box : la seule que l'on puisse connaître côté serveur. Derrière un CGNAT, même cette adresse serait partagée par d'autres abonnés.

Dans l'autre sens, quand sig-app-1 lance apt update, ses paquets partent avec l'adresse source 172.16.20.11 vers sa route par défaut, la passerelle publique 172.16.20.1. La passerelle fait un NAT source vers son adresse publique : le dépôt Ubuntu voit une connexion venue de la passerelle, comme il verrait n'importe quelle box. Scaleway appelle ce mécanisme le NAT dynamique, activé automatiquement quand la passerelle est rattachée à un réseau privé et annonce la route par défaut.

Le NAT en épingle

Une situation déroute régulièrement : depuis sig-app-1, à l'intérieur du réseau privé, on appelle l'adresse publique du service, 203.0.113.10, par exemple parce que l'application s'appelle elle-même par son nom de domaine public. Le paquet doit sortir vers l'adresse publique puis revenir dans le même réseau. On parle de NAT en épingle (hairpin NAT, ou NAT loopback).

Avec un NAT de destination fait par une box, cela échoue souvent : le serveur reçoit la requête avec l'adresse source privée du client (192.168.1.23), répond directement à cette adresse sur le réseau local, sans passer par la box, et le client reçoit une réponse d'une adresse qu'il n'a pas contactée, qu'il ignore. Un NAT correct traduit aussi la source dans ce cas, pour forcer la réponse à repasser par lui : c'est ce qu'exige la RFC 5382 pour TCP (« A NAT MUST support "hairpinning" for TCP »). Beaucoup de box ne le font pas. La solution propre consiste à donner aux clients internes une résolution DNS qui pointe vers l'adresse interne, ou, mieux encore, à faire appeler les services internes par leur adresse interne.

Sous le capot

Où se fait la traduction dans le noyau

Netfilter offre des points d'accroche (hooks) sur le trajet d'un paquet dans le noyau. Pour un paquet qui traverse la machine (cas d'un routeur NAT), l'ordre est le suivant :

    flowchart LR
  E["Interface<br/>d'entrée"] --> PR["prerouting<br/>(DNAT)"]
  PR --> R{"Décision<br/>de routage"}
  R -->|"pour la machine"| IN["input"]
  R -->|"pour ailleurs"| FW["forward<br/>(filtrage)"]
  FW --> PO["postrouting<br/>(SNAT, masquerade)"]
  PO --> S["Interface<br/>de sortie"]
  

Le suivi de connexion intervient dès le début, avant prerouting : c'est lui qui rattache le paquet à une entrée existante ou en crée une nouvelle à l'état new. Les chaînes de type nat ne sont consultées que pour le premier paquet d'un flux ; leur décision est inscrite dans l'entrée. Les paquets suivants sont traduits à partir de l'entrée, ce qui rend le NAT peu coûteux une fois la connexion ouverte.

Ce que la traduction réécrit vraiment

Changer une adresse dans un en-tête IP ne suffit pas. L'en-tête IPv4 contient une somme de contrôle calculée sur ses propres champs : elle doit être recalculée. Plus subtil, les sommes de contrôle de TCP et d'UDP couvrent aussi un « pseudo-en-tête » qui contient les adresses IP source et destination (RFC 768 pour UDP) : changer l'adresse oblige donc à corriger aussi la somme de contrôle de la couche transport, même si le port ne change pas. Le noyau fait ces corrections de façon incrémentale, sans tout recalculer.

Les messages d'erreur ICMP posent un problème particulier : ils transportent dans leurs données le début du paquet qui a causé l'erreur, avec ses adresses d'origine. Le suivi de connexion reconnaît ces messages comme related à la connexion concernée, et les traduit aussi, contenu compris. Sans cela, un message « paquet trop gros » ne retrouverait jamais l'émetteur, et la découverte de la MTU du chemin (leçon 6) échouerait.

Une table finie

La table de suivi de connexion a une taille maximale, nf_conntrack_max. Quand elle est pleine, le noyau ne peut plus créer d'entrée pour un nouveau flux : il jette le premier paquet de toute nouvelle connexion, et écrit dans le journal du noyau un message reconnaissable :

nf_conntrack: table full, dropping packet

Ce message, cité par de nombreux retours d'expérience d'hébergeurs et d'équipementiers, est souvent accompagné de lignes indiquant que d'autres messages identiques ont été supprimés par limitation de débit. Les connexions déjà établies continuent de fonctionner ; les nouvelles échouent par délai d'attente dépassé, de façon intermittente, alors que le processeur et la mémoire semblent au repos.

Les causes typiques : un serveur qui reçoit énormément de connexions courtes (une API très sollicitée, un résolveur DNS), une attaque par inondation, ou des délais d'expiration trop longs qui gardent des entrées mortes. Les remèdes : augmenter nf_conntrack_max en connaissance de cause (chaque entrée consomme de la mémoire noyau), raccourcir les délais d'expiration adaptés au trafic, ou exclure du suivi des flux qui n'en ont pas besoin (état untracked).

Pièges courants

Croire que le serveur voit l'adresse du client. Derrière un répartiteur, un mandataire ou un NAT, l'adresse source vue par l'application est celle du dernier équipement. Lire X-Forwarded-For (ou le protocole PROXY, qui transmet l'adresse d'origine au début de la connexion TCP) est indispensable, et il ne faut faire confiance à cet en-tête que s'il a été ajouté par votre répartiteur : un client peut l'envoyer lui-même avec la valeur de son choix.

Les connexions qui meurent après quelques minutes d'inactivité. Une connexion à une base de données, gardée ouverte dans un pool, se tait pendant dix minutes, puis la requête suivante reste bloquée longtemps avant d'échouer. Un NAT sur le chemin a effacé la correspondance : les paquets suivants arrivent sur un port inconnu. AWS documente par exemple qu'une connexion inactive pendant 350 secondes à travers une passerelle NAT est coupée, et que la passerelle renvoie alors un paquet de réinitialisation (RST) aux machines qui tentent de la poursuivre. Remède : activer les messages de maintien de TCP (keepalive) avec une période plus courte que le délai du NAT, ou demander au pool de vérifier ses connexions avant usage.

Bloquer une adresse et bloquer tout le monde. Une règle anti-abus qui bannit une adresse peut couper tous les abonnés d'un CGNAT ou tous les employés d'une entreprise. Limitez plutôt par compte, par jeton, ou par adresse avec des seuils élevés.

La redirection de port qui « ne marche pas ». Le DNAT est en place, mais rien n'arrive : vérifiez que ip_forward vaut 1 sur le routeur, que la chaîne forward laisse passer le flux, que la machine de destination a bien le routeur comme route de retour (sinon sa réponse part ailleurs), et que l'opérateur ne vous a pas placé derrière un CGNAT, auquel cas aucune redirection sur votre box ne sera jamais joignable.

Un masquerade trop large. Une règle masquerade sans condition sur l'interface de sortie traduit aussi le trafic entre réseaux internes. Tout semble fonctionner, mais les journaux de chaque serveur n'affichent plus que l'adresse du routeur, et les règles de filtrage par adresse source deviennent inopérantes.

Sécurité

Le NAT n'est pas un pare-feu

Le NAT a un effet de filtrage implicite : un paquet non sollicité qui arrive de l'extérieur ne correspond à aucune entrée, n'a pas de destination, et il est jeté. C'est pourquoi on entend dire qu'une machine « derrière un NAT » est protégée. Cette protection est réelle mais accidentelle, et elle s'arrête vite :

  • Elle ne s'applique qu'aux connexions entrantes non sollicitées. Un logiciel malveillant sur une machine interne ouvre des connexions sortantes, que le NAT laisse passer et pour lesquelles il ouvre complaisamment le chemin du retour.
  • Elle dépend du comportement de filtrage. Avec un filtrage indépendant de la destination, recommandé par la RFC 4787 pour faciliter le pair à pair, n'importe quelle machine d'Internet peut envoyer des paquets sur un port public déjà ouvert par une machine interne.
  • Elle disparaît avec la première redirection de port, et avec des mécanismes qui en créent automatiquement, comme UPnP sur les box domestiques.
  • Elle disparaît avec IPv6, où chaque machine a de nouveau une adresse publique (leçon 8). Une politique de sécurité qui reposait sur le NAT laisse alors les machines exposées.

La sécurité d'un réseau se décrit par des règles de filtrage explicites, à état, avec une politique par défaut qui refuse : c'est le rôle d'un pare-feu ou d'un groupe de sécurité. Le NAT traduit ; il ne décide pas.

Les journaux et la traçabilité

La traduction efface de l'information. Pour enquêter sur un incident, il faut pouvoir remonter de « l'adresse publique 198.51.100.7, port 40312, à 14 h 03 min 12 s » à une machine interne. Cela suppose des journaux de traduction côté opérateur ou côté passerelle, des horloges synchronisées, et, côté application, la journalisation du port source et de l'en-tête X-Forwarded-For fourni par votre répartiteur.

L'inondation de la table

Une table de suivi de connexion pleine est un déni de service : un attaquant qui envoie des paquets d'ouverture de connexion depuis de nombreuses adresses usurpées peut remplir la table d'un pare-feu ou d'un routeur NAT et bloquer toutes les connexions légitimes. Surveillez nf_conntrack_count par rapport à nf_conntrack_max sur les machines qui font du NAT ou du filtrage à état, et alertez bien avant 100 %.

En production

Le NAT dans le cloud

Les fournisseurs proposent tous une passerelle NAT gérée pour donner une sortie Internet à des machines sans adresse publique (glossaire : passerelle NAT) :

FournisseurServiceParticularités documentées
ScalewayPublic GatewayNAT dynamique automatique ; NAT statique optionnel (port public vers adresse et port privés) ; bastion SSH
AWSNAT Gatewayjusqu'à 55 000 connexions simultanées par adresse IPv4 et par destination unique ; connexion inactive coupée après 350 secondes
AzureNAT Gatewayports SNAT attribués dynamiquement à toutes les machines d'un sous-réseau
Google CloudCloud NATservice distribué et logiciel, sans machine mandataire, intégré au réseau virtuel

L'épuisement des ports source

Une adresse publique offre environ 64 000 ports par protocole. Un NAT qui traduit les connexions de nombreuses machines vers une même destination (une API de paiement, un registre d'images, une base de données externe) est limité par le nombre de couples adresse publique et port disponibles pour cette destination. Au-delà, plus aucune nouvelle connexion ne peut s'ouvrir : c'est l'épuisement des ports SNAT (SNAT port exhaustion).

Microsoft en a fait l'un des sujets principaux de son guide de dépannage des connexions sortantes : selon sa documentation, la plupart des problèmes de connectivité sortante rencontrés par ses clients viennent de l'épuisement des ports SNAT et des délais d'expiration. Un répartiteur de charge Azure attribue par défaut un nombre fixe et prudent de ports à chaque machine (1 024 dans l'exemple de sa documentation) ; une passerelle NAT partage dynamiquement les ports entre toutes les machines du sous-réseau.

Les remèdes sont les mêmes partout, et ils sont d'abord applicatifs :

  • Réutiliser les connexions : HTTP/1.1 avec maintien de connexion, HTTP/2, pools de connexions vers les bases de données. Une application qui ouvre une connexion TCP par requête consomme un port pour chaque requête, puis le garde bloqué le temps que la connexion fermée expire.
  • Limiter les tentatives agressives, qui ouvrent des connexions plus vite que les anciennes ne se libèrent.
  • Ajouter des adresses publiques à la passerelle quand le besoin est réel : chaque adresse ajoute une réserve de ports.
  • Éviter le NAT pour les services du même fournisseur, par des points d'accès privés.

Ce que Lyneko applique

Pour les applications de Lyneko comme pour Signalements, la règle est la même : les instances et les nœuds n'ont pas d'adresse publique, la sortie passe par une passerelle NAT, l'entrée par un répartiteur de charge (ou par l'ingress du cluster), et les applications journalisent l'en-tête X-Forwarded-For ajouté par ce répartiteur, sans faire confiance à celui que pourrait envoyer un client.

Exercices

1. Lire une traduction (niveau 100). Le portable 192.168.1.23 ouvre deux connexions simultanées vers 203.0.113.10:443, depuis les ports 51514 et 51515. Le téléphone 192.168.1.42 en ouvre une depuis le port 51514. La box a l'adresse publique 198.51.100.7. Proposez une table de traduction possible, et expliquez pourquoi la box ne peut pas garder le port 51514 pour les deux appareils.

Solution

Par exemple : 192.168.1.23:51514 ↔ 198.51.100.7:51514, 192.168.1.23:51515 ↔ 198.51.100.7:51515, 192.168.1.42:51514 ↔ 198.51.100.7:40001. Vu du serveur, deux connexions venues de 198.51.100.7:51514 vers 203.0.113.10:443 seraient indiscernables : le serveur identifie une connexion par le quintuplet (protocole, adresse et port source, adresse et port destination), et la réponse ne pourrait pas être rendue au bon appareil. La box doit donc attribuer un port public distinct au téléphone. C'est toute la différence entre NAT de base et NAPT.

2. Où est le client ? (niveau 200). Les journaux de Gunicorn sur sig-app-1 montrent toutes les requêtes venant de 172.16.20.5. Un client se plaint d'avoir été bloqué. Quelles informations faut-il journaliser, et à quels endroits, pour pouvoir retrouver ses requêtes ? Que pourra-t-on savoir, et que ne pourra-t-on pas savoir s'il est derrière un CGNAT ?

Solution

172.16.20.5 est l'adresse du répartiteur dans le réseau privé. Il faut journaliser, côté application, l'en-tête X-Forwarded-For ajouté par le répartiteur (en ne retenant que la valeur ajoutée par lui, la plus à droite, et pas ce que le client aurait pu y mettre), et, côté répartiteur, l'adresse et le port source de la connexion du client, avec une heure précise. On obtient l'adresse publique sous laquelle le client apparaît, par exemple 198.51.100.7. Derrière un CGNAT, cette adresse est partagée par de nombreux abonnés : seul l'opérateur, avec ses journaux de traduction, l'adresse, le port et l'heure exacte, peut remonter à un abonné, et uniquement dans un cadre légal. Côté serveur, on ne peut jamais connaître l'adresse privée de l'appareil.

3. Le pool qui se fige (niveau 200). Une application sur sig-app-1 garde un pool de connexions vers une API externe. Chaque matin, la première requête reste bloquée plusieurs minutes avant d'échouer, puis tout fonctionne. La nuit, l'application ne fait aucune requête. Proposez une explication et deux corrections.

Solution

Les connexions du pool traversent la passerelle NAT. Pendant la nuit, elles restent silencieuses plus longtemps que le délai d'expiration des correspondances de la passerelle : les entrées sont effacées. Le matin, l'application envoie une requête sur une connexion qu'elle croit ouverte ; le paquet arrive à la passerelle sans correspondance et est jeté (ou provoque une réinitialisation, selon l'équipement), et TCP retransmet jusqu'à abandonner. Corrections : activer les messages de maintien TCP (SO_KEEPALIVE, avec une période plus courte que le délai du NAT, réglable par net.ipv4.tcp_keepalive_time ou par socket), ou configurer le pool pour qu'il vérifie les connexions avant usage et ferme celles restées inactives trop longtemps.

4. Écrire les règles (niveau 200). Sur une machine virtuelle jetable à deux interfaces, ens2 (adresse publique fixe 203.0.113.50) et ens3 (réseau 10.20.0.0/24), écrivez un jeu de règles nftables qui : fait sortir le réseau privé avec l'adresse 203.0.113.50, et redirige le port 443 de l'adresse publique vers 10.20.0.10:8443. Quelles autres conditions faut-il réunir pour que la redirection fonctionne ?

Solution
table ip nat {
  chain prerouting {
    type nat hook prerouting priority -100; policy accept;
    iifname "ens2" ip daddr 203.0.113.50 tcp dport 443 dnat to 10.20.0.10:8443
  }
  chain postrouting {
    type nat hook postrouting priority 100; policy accept;
    ip saddr 10.20.0.0/24 oifname "ens2" snat to 203.0.113.50
  }
}

snat to plutôt que masquerade, puisque l'adresse est fixe. Il faut aussi : net.ipv4.ip_forward=1 ; une chaîne de filtrage forward qui autorise le flux vers 10.20.0.10:8443 (et les réponses, par ct state established,related accept) ; que 10.20.0.10 ait la machine NAT comme passerelle par défaut, pour que ses réponses repassent par elle et soient traduites à l'envers ; et que le service écoute bien sur le port 8443 de 10.20.0.10.

Récapitulatif

  • La pénurie d'adresses IPv4 a rendu la traduction d'adresses universelle. Les blocs RFC 1918 (10/8, 172.16/12, 192.168/16) ne sont pas routables sur Internet.
  • Le NAPT réécrit l'adresse et le port source, ce qui permet à des milliers de connexions de partager une adresse publique. Sous Linux : snat pour une adresse fixe, masquerade pour l'adresse de l'interface de sortie.
  • La traduction repose sur le suivi de connexion : une entrée par flux, avec ses deux sens, des états (new, established, related, invalid, untracked) et des délais d'expiration. Une table pleine jette les nouvelles connexions (nf_conntrack: table full, dropping packet).
  • Le DNAT (redirection de port) rend joignable une machine privée ; il se fait avant le routage (prerouting), le SNAT après (postrouting).
  • Le CGNAT (100.64.0.0/10) fait partager une adresse publique à de nombreux abonnés : une adresse n'identifie pas une personne.
  • Le NAT casse le modèle de bout en bout, les protocoles qui transportent des adresses, et les connexions silencieuses trop longues.
  • Le NAT n'est pas un pare-feu. Il disparaît avec IPv6, et ses effets de bord (adresse client masquée, ports épuisés) se gèrent dans l'application.

Pour aller plus loin

  • Les RFC 4787 et 5382, courtes et très concrètes, pour comprendre ce qu'on est en droit d'attendre d'un NAT.
  • La documentation des réglages du suivi de connexion dans la documentation du noyau, pour dimensionner une machine qui fait du NAT ou du filtrage à état.
  • Le cours Le réseau sous Linux : nftables, namespaces, bridges, qui construit un routeur NAT complet dans des espaces de noms réseau.
  • La leçon suivante, où chaque machine retrouve une adresse publique et où le NAT n'a plus de raison d'être.
Voir ma constellation →

Sources