SSD Nodes Learn 🎉 VPS dès $5.50/mois
Guides Matt ConnorPar Matt Connor

WireGuard : comprendre le cryptokey routing et AllowedIPs

AllowedIPs sert à la fois de table de routage et de contrôle d’accès. Comprenez le handshake Noise, la rotation des clés et chaque ligne de wg0.conf.

Fonctionnement de WireGuard, en une idée

WireGuard associe chaque paquet à une clé publique. Ce mécanisme porte un nom : le routage par clé cryptographique, ou cryptokey routing. Il constitue toute l’architecture : la ligne AllowedIPs située à côté d’un pair sert de table de routage pour les paquets qui quittent votre machine et de liste de contrôle d’accès pour les paquets reçus de ce pair. Un seul paramètre, deux fonctions. Lisez AllowedIPs de cette manière et chaque fichier de configuration WireGuard devient compréhensible.

Il n’existe ni table de sessions indexée par adresse IP ni base de données d’utilisateurs. Un pair correspond à une clé publique et à l’ensemble des adresses que cette clé peut utiliser. Le handshake et les temporisations servent à maintenir cette association lorsque le réseau sous-jacent change. Si vous voulez d’abord mettre en place un tunnel fonctionnel, créez-en un avec un VPN WireGuard auto-hébergé sur votre propre VPS, puis revenez ici lorsqu’une ligne de configuration vous surprend.

wg0 est une table de routage et une liste de contrôle d’accès

Commencez par le trafic sortant. Votre kernel route un paquet vers le périphérique wg0 de la manière habituelle, via la table de routage principale. WireGuard compare ensuite l’adresse de destination de ce paquet à une table contenant les préfixes autorisés de chaque peer, en commençant par le préfixe le plus spécifique. Une correspondance désigne un peer, qui désigne une clé publique, elle-même associée à une clé de session et à un endpoint UDP. Le paquet est chiffré pour ce peer, puis envoyé vers cet endpoint.

Si aucun AllowedIPs de peer ne couvre la destination, rien n’est envoyé, car aucune clé ne permet de le chiffrer.

ping: sendmsg: Required key not available

Cette erreur n’a qu’une seule signification : l’adresse que vous avez tenté d’atteindre ne figure sous aucun peer. Une autre erreur, ping: sendmsg: Destination address required, signifie qu’un peer a bien correspondu, mais que WireGuard ne possède aucun endpoint pour celui-ci, car aucun endpoint n’a été configuré ni appris jusqu’à présent.

Passons maintenant au trafic entrant. Un paquet UDP arrive sur le port d’écoute. WireGuard retrouve la session à partir de l’index du récepteur présent dans l’en-tête, vérifie le compteur avec une fenêtre glissante de protection contre les rejeux, puis déchiffre et authentifie la charge utile. Ce n’est qu’ensuite qu’il lit le paquet interne. L’adresse source de ce paquet interne doit se trouver dans le AllowedIPs du peer émetteur. Dans le cas contraire, le paquet est abandonné. Lorsque le débogage dynamique est activé, le kernel affiche la raison, sur une ligne comme celle-ci :

wg0: Packet has unallowed src IP (10.8.0.9) from peer 2 (203.0.113.10:51820)

C’est pourquoi un peer côté serveur reçoit un /32. Un peer configuré avec AllowedIPs = 10.8.0.2/32 peut envoyer des paquets depuis 10.8.0.2, et depuis aucune autre adresse. Remplacez cette valeur par 0.0.0.0/0 et ce client pourra injecter des paquets en usurpant n’importe quelle adresse source située dans votre tunnel, y compris celle d’un autre client.

Les préfixes qui se chevauchent sont résolus selon leur spécificité, car la recherche utilise la correspondance de préfixe la plus longue. Deux préfixes identiques associés à deux peers se comportent différemment : l’entrée est attribuée au peer configuré en dernier, et le premier peer cesse de recevoir ce trafic sans qu’aucune erreur soit écrite. wg show wg0 allowed-ips affiche la table réellement présente dans le kernel. C’est cette table qui fait foi lorsque le fichier sur disque et l’état en cours d’exécution ne correspondent plus.

Lire un fichier de configuration en tenant compte du routage par clés cryptographiques

Côté serveur :

[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <server private key>

[Peer]
PublicKey = <laptop public key>
AllowedIPs = 10.8.0.2/32

Côté client :

[Interface]
Address = 10.8.0.2/32
PrivateKey = <laptop private key>

[Peer]
PublicKey = <server public key>
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

Le même mot-clé n’a pas le même rôle des deux côtés. Côté client, il signifie « envoyer chaque destination vers ce pair ». Côté serveur, il signifie « n’accepter que cette adresse depuis ce pair ». L’asymétrie vient des valeurs, pas du rôle.

La moitié de ces clés ne relève pas du protocole. Address, DNS, MTU, PostUp et SaveConfig appartiennent à wg-quick, le script shell qui active l’interface. Le noyau ne les voit jamais. wg-quick strip wg0 affiche la configuration réduite que l’outil wg charge réellement. C’est le moyen le plus rapide de voir cette séparation.

Ce que fait réellement le handshake

Le handshake de WireGuard utilise Noise_IKpsk2, issu du Noise Protocol Framework. La partie IK est utile pour l’administration système : la clé publique statique du responder est déjà connue de l’initiator, car il s’agit de la PublicKey dans votre bloc [Peer], et l’initiator envoie sa propre clé publique statique dans le premier message, chiffrée. Il n’y a donc ni échange de certificats ni aller-retour d’identification. Un observateur passif ne peut pas déterminer quelle clé appelle, sauf s’il possède la clé privée du responder.

Le coût est d’un aller-retour. Le message d’initiation fait 148 octets, la réponse 92 octets, puis les données circulent immédiatement. Chaque côté génère une nouvelle paire de clés éphémères Curve25519 à chaque handshake, et les clés de session proviennent d’une chaîne de résultats Diffie-Hellman qui combine les clés statiques et éphémères. Les clés privées éphémères sont ensuite supprimées, ce qui assure la forward secrecy : une personne qui enregistre votre trafic aujourd’hui et vole la clé privée du serveur l’année prochaine ne pourra toujours pas lire les données enregistrées.

Une initiation de handshake contient un timestamp TAI64N, et chaque peer mémorise le timestamp le plus élevé qu’il a reçu de l’autre. Une initiation rejouée est donc rejetée. Les paquets de données contiennent un compteur 64 bits utilisé comme nonce, et le récepteur conserve une fenêtre glissante des compteurs récemment vus. Les replays et les réordonnancements importants sont ainsi gérés sans état de connexion comparable à celui de TCP.

Les clés de session ne durent pas longtemps, et les timers sont intégrés au code plutôt que configurables.

ChartWireGuard protocol timers, in seconds
The data behind this chart
[
  {
    "label": "REKEY_TIMEOUT",
    "seconds": 5,
    "notes": "resend a handshake initiation that got no answer"
  },
  {
    "label": "KEEPALIVE_TIMEOUT",
    "seconds": 10,
    "notes": "send a keepalive after receiving data and sending none back"
  },
  {
    "label": "REKEY_ATTEMPT_TIME",
    "seconds": 90,
    "notes": "give up on the handshake and report the peer as down"
  },
  {
    "label": "REKEY_AFTER_TIME",
    "seconds": 120,
    "notes": "sender begins a fresh handshake for a new session key"
  },
  {
    "label": "REJECT_AFTER_TIME",
    "seconds": 180,
    "notes": "the old session key is refused and traffic stops"
  }
]

Ce sont des constantes de la spécification du protocole, pas des mesures. Le 5 d’entre elles pilote tout le cycle de vie de la session. Après 120 secondes d’utilisation, l’émetteur lance un nouveau handshake. Après 180 secondes, l’ancienne clé est refusée définitivement, et le trafic s’arrête jusqu’à la fin d’un nouveau handshake. Une initiation qui ne reçoit aucune réponse est renvoyée toutes les 5 secondes, puis abandonnée après 90 secondes. C’est pourquoi wg show affiche latest handshake sous la forme d’un âge relatif, et pourquoi un tunnel sain et actif conserve une valeur faible. Si l’âge augmente alors que vous envoyez activement du trafic, cela signifie que les handshakes échouent, et non que le tunnel est inactif.

Pourquoi un pair n’a pas de rôle client ou serveur

Les deux extrémités exécutent un code identique et utilisent le même format de configuration. Il n’existe pas de mode serveur. L’asymétrie que vous percevez vient de Endpoint, et Endpoint est facultatif.

Un pair dont le point de terminaison est configuré peut lancer une négociation. Un pair qui n’en a pas attend, puis apprend l’adresse et le port de l’autre côté grâce au premier paquet correctement authentifié. Ce point de terminaison appris est mémorisé et mis à jour chaque fois qu’un paquet valide arrive depuis une nouvelle adresse. C’est ainsi que fonctionne le roaming : un ordinateur portable qui passe du Wi-Fi à un réseau mobile conserve le même tunnel, car une session est identifiée par une clé et un index, et non par une adresse IP. Rien ne se reconnecte, car rien n’a jamais été connecté au sens TCP du terme.

Le même mécanisme entraîne un fait important à connaître : le pair qui possède l’adresse publique conserve toujours la dernière adresse IP publique connue de l’autre côté, et wg show l’affiche.

Primitives fixes, sans négociation

WireGuard ne propose aucune liste de suites cryptographiques. ChaCha20-Poly1305 assure le chiffrement authentifié, Curve25519 l’accord de clé, BLAKE2s le hachage et HKDF la dérivation de clé. Tous les déploiements utilisent ces primitives. Il n’y a donc aucune phase de négociation à analyser ni aucun mécanisme de repli vers une option plus faible. Le compromis est réel : si l’une de ces primitives est cassée, il faut publier une nouvelle version de l’ensemble du protocole et la déployer aux deux extrémités, et non modifier la configuration. Cette décision à elle seule supprime la majeure partie du code et des modes d’échec d’un tunnel basé sur TLS. C’est essentiellement ce qui ressort de la comparaison WireGuard face à OpenVPN.

Pourquoi le port ne répond pas à un scanner

Chaque message de handshake contient un champ appelé mac1. Il s’agit d’un MAC (code d’authentification de message) calculé sur le message avec une clé dérivée de la clé publique statique du répondeur. Un émetteur qui ne connaît pas cette clé publique ne peut pas produire de mac1 valide. Le récepteur abandonne alors le paquet sans envoyer de réponse. Il n’y a ni erreur, ni réinitialisation, ni message ICMP.

Le résultat visible est un scan UDP qui ne reçoit aucune réponse.

sudo nmap -sU -p 51820 vpn.example.com

nmap indique open|filtered. C’est la même réponse que pour un port dont les paquets sont ignorés silencieusement par un pare-feu. Le port se comporte de la même manière, que WireGuard soit à l’écoute ou non, du moins pour les personnes qui ne possèdent pas déjà votre clé publique.

Un second champ, mac2, gère la pression liée aux attaques par déni de service. Lorsque le récepteur est sous charge, il répond à une initiation valide avec une réponse cookie de 64 octets, liée à l’adresse source de l’émetteur. Il refuse d’effectuer des opérations coûteuses de cryptographie à clé publique tant que l’émetteur ne lui renvoie pas ce cookie. Cela confirme que l’adresse source est réelle avant de consommer du temps CPU, et ce mécanisme ne s’active que sous charge.

Pourquoi 0.0.0.0/0 transforme un peer en route par défaut

Comme AllowedIPs est la table de routage, AllowedIPs = 0.0.0.0/0, ::/0 revendique toutes les destinations pour ce peer. C’est tout le principe du full tunnel.

Le routage qui permet son fonctionnement est plus intéressant que la ligne elle-même. Une route par défaut classique via wg0 créerait une boucle, car le paquet UDP chiffré qui transporte votre trafic doit lui aussi quitter la machine et correspondrait à sa propre route par défaut. wg-quick évite cela avec le policy routing. Il marque les paquets sortants de WireGuard avec un fwmark, place la route par défaut du tunnel dans une table de routage distincte et ajoute des règles afin que seul le trafic non marqué l’emprunte. Exécutez ip rule show pour voir le résultat :

32764:	from all lookup main suppress_prefixlength 0
32765:	not from all fwmark 0xca6c lookup 51820
32766:	from all lookup main

0xca6c correspond à 51820 en hexadécimal, et 51820 est également le numéro de la table. La règle suppress_prefixlength 0 fait en sorte que la table principale ignore sa propre route par défaut. Les routes spécifiques, comme celle de votre sous-réseau local, restent donc prioritaires, tandis que tout le reste passe dans la table du tunnel. Un split tunnel n’a besoin de rien de tout cela : une liste plus restreinte, comme AllowedIPs = 10.8.0.0/24, 10.20.0.0/16, devient une série de routes ordinaires dans la table principale.

Un full tunnel ne règle pas à lui seul la résolution des noms, car le resolver appris par votre client auprès du réseau local reste généralement utilisé et sa route est plus spécifique. C’est une tâche distincte, traitée dans DNS qui fuit en dehors d’un tunnel WireGuard.

À quoi sert réellement PersistentKeepalive

WireGuard n’envoie rien lorsqu’il n’y a aucun trafic. Il n’y a ni heartbeat, ni actualisation de session, ni paquet sur le réseau. Ce silence économise la batterie, facilite le cas du scanner décrit plus haut et pose problème dans une configuration précise.

Un peer situé derrière un NAT (network address translation) ou un pare-feu stateful n’est joignable depuis l’extérieur que tant qu’un mapping existe sur cet équipement, et ce mapping a été créé par un paquet sortant. La durée courante d’un mapping UDP commence autour de 30 secondes. Une fois le mapping expiré, le middlebox bloque les paquets provenant du côté public, et le tunnel semble arrêté jusqu’à ce que le peer situé derrière le NAT envoie quelque chose. PersistentKeepalive = 25 envoie toutes les 25 secondes un paquet authentifié vide. Cette fréquence reste inférieure à la durée courante la plus courte et maintient donc le mapping ouvert.

Configurez-le sur le peer situé derrière le NAT. Un serveur disposant d’une adresse publique et d’un port UDP ouvert n’en a pas besoin. Le configurer sur ce serveur ajoute uniquement du trafic. Ne le confondez pas avec le keepalive automatique, qui est envoyé 10 secondes après la réception de données par un peer lorsqu’il n’a rien à envoyer en retour. Celui-ci est toujours actif et ne peut pas être configuré.

Le routage de votre LAN dans le tunnel n’est pas une fonctionnalité de WireGuard

Supposons que le pair B se trouve sur un réseau domestique 192.168.50.0/24 et que le pair A doive y accéder. Deux systèmes distincts doivent être configurés correctement, et un seul d’entre eux est WireGuard.

Côté WireGuard : ajoutez 192.168.50.0/24 à AllowedIPs de B sur A. A route ainsi ce préfixe vers B et accepte les paquets provenant de B dont les adresses source appartiennent à ce préfixe. Sans cette configuration, le routage par cryptographic keys ne dispose d’aucune clé pour la destination ni d’aucune autorisation pour la source.

Côté kernel : sur B, net.ipv4.ip_forward doit être défini sur 1, sinon le kernel supprime tous les paquets déchiffrés qui ne sont pas destinés à B lui-même. La chaîne forward du firewall de B doit autoriser le trafic. Les hôtes du LAN doivent disposer d’une route de retour vers 10.8.0.0/24, ou B doit appliquer une source NAT afin que les réponses repassent par B.

Le rôle de WireGuard s’arrête lorsqu’il remet le paquet déchiffré au kernel. Tout ce qui suit relève du routage et du filtrage Linux ordinaires. C’est pourquoi cette panne apparaît dans les compteurs nft list ruleset ou dans ip -s link show wg0, et non dans wg show. Si vous préférez gérer les pairs avec une interface web, exécuter wg-easy dans Docker génère les entrées des pairs pour vous, mais les règles de forwarding restent à la charge de l’hôte.

Pourquoi WireGuard fonctionne dans le kernel

wg0 est un pilote de périphérique réseau. Les paquets y parviennent via la pile de routage standard, sont chiffrés dans le contexte softirq, puis sortent par un socket UDP sans jamais passer dans l’espace utilisateur. C’est ce qui permet d’obtenir ce débit. C’est aussi pourquoi le module reste limité à environ quatre mille lignes de code, ce qui facilite sa revue et son intégration dans le noyau Linux mainline 5.6 en mars 2020. Ubuntu 24.04 et Debian 13 l’intègrent. Seul le paquet wireguard-tools manque donc.

Le fait qu’il s’agisse d’une interface standard a des conséquences pratiques. tcpdump -ni wg0 affiche les paquets internes en clair, tandis que tcpdump -ni eth0 udp port 51820 affiche les paquets externes chiffrés. Les comparer permet d’identifier immédiatement le sens dans lequel se situe la panne. netfilter et la mise en forme du trafic traitent wg0 comme n’importe quelle autre liaison. Lorsque le module du kernel n’est pas disponible, par exemple avec une virtualisation de conteneurs qui partage le kernel de l’hôte, wireguard-go implémente le même protocole dans l’espace utilisateur via un périphérique TUN. Le débit diminue réellement, car chaque paquet franchit deux fois la frontière entre le kernel et l’espace utilisateur.

Ce que WireGuard ne protège pas

Le modèle de menace est volontairement limité, et un protocole aussi discret peut facilement donner lieu à des conclusions infondées. Il faut le dire clairement.

  • Il ne dissimule pas l’utilisation de WireGuard. Les messages de handshake ont des tailles fixes, le premier octet indique le type de message et le transport utilise UDP. L’inspection approfondie des paquets le reconnaît facilement, et un réseau qui bloque les VPN peut l’interdire. L’obfuscation a été volontairement exclue.
  • Il ne dissimule ni le volume ni le timing. Les données utiles sont complétées uniquement jusqu’à une limite de 16 octets. Un observateur voit donc toujours quand vous envoyez des données et en estime le volume.
  • Il conserve le dernier endpoint connu. Le peer dont l’adresse est publique enregistre l’adresse IP publique actuelle de l’autre côté, et wg show l’affiche. Associée à une adresse de tunnel fixe dans la configuration, cette information constitue un identifiant stable qui suit l’utilisateur d’un réseau à l’autre. Sur votre propre VPS, ce n’est pas un problème. C’est aussi pour cette raison que les services commerciaux ajoutent une couche au-dessus du protocole.
  • Il authentifie une clé, pas une personne. Le peer est la personne qui détient le fichier de clé privée. Définissez /etc/wireguard en mode 700 et les fichiers de clés en mode 600.
  • Il n’existe ni liste de révocation ni expiration. L’accès prend fin lorsque vous supprimez l’entrée du peer de chaque serveur qui la contient, et les clés statiques restent valides jusqu’à leur suppression.

Rien de tout cela ne rend WireGuard faible. Cela le rend compact, et c’est précisément le but : il authentifie et chiffre, puis laisse la gestion des identités et l’attribution des adresses à ce que vous construisez au-dessus. Une couche de coordination du type décrit dans WireGuard comparé à Tailscale comble précisément cette lacune, en utilisant le même data plane que celui présenté plus haut.

FAQ

Qu’est-ce que le cryptokey routing dans WireGuard ?

Le cryptokey routing est la règle qui associe chaque paquet à une clé publique. Chaque entrée de peer contient une liste de préfixes dans AllowedIPs. En sortie, WireGuard sélectionne le peer en comparant la destination du paquet avec la liste de chaque peer, en privilégiant le préfixe le plus long. Cette liste fait donc office de table de routage. En entrée, une fois le paquet déchiffré et authentifié, son adresse source interne doit appartenir à cette même liste. Sinon, le paquet est rejeté. La liste fait donc aussi office de liste de contrôle d’accès. WireGuard n’a ni configuration de routage distincte ni pare-feu interne distinct, car cette même liste assure les deux fonctions.

Dois-je configurer PersistentKeepalive sur les deux peers ?

Non. Configurez-le du côté situé derrière un NAT (network address translation) ou un pare-feu stateful, généralement le client. WireGuard n’envoie rien lorsqu’il est inactif. La translation qui permet au côté distant de joindre ce peer expire donc, souvent en moins d’une minute, et le tunnel semble alors inutilisable dans un sens. PersistentKeepalive = 25 envoie un paquet authentifié vide toutes les 25 secondes et maintient la translation active. Un peer doté d’une adresse publique et d’un port UDP ouvert n’en a pas besoin.

Pourquoi ping sur le tunnel affiche-t-il « Required key not available » ?

Parce que l’adresse de destination ne se trouve dans le AllowedIPs d’aucun peer. Le cryptokey routing n’a donc trouvé aucune clé avec laquelle chiffrer le paquet, et le kernel a refusé de l’envoyer. Exécutez wg show wg0 allowed-ips et comparez sa sortie avec l’adresse que vous essayez de joindre avec ping. L’erreur similaire Destination address required correspond à un autre problème : un peer a été sélectionné, mais WireGuard ne possède aucun endpoint pour celui-ci, car aucun endpoint n’a été configuré et aucun paquet authentifié n’est encore arrivé de ce peer.

Un pare-feu peut-il détecter et bloquer WireGuard ?

Oui. WireGuard authentifie et chiffre votre trafic, sans chercher à dissimuler son fonctionnement. Les messages de handshake ont une taille fixe de 148 et 92 octets. Le premier octet de chaque message indique son type. Le transport utilise UDP. La deep packet inspection identifie donc facilement le protocole. Les réseaux qui bloquent UDP ou qui identifient les protocoles par leur fingerprinting l’arrêteront. Dissimul­er le tunnel consiste à l’encapsuler dans autre chose. Il s’agit d’un outil distinct, et non d’un paramètre WireGuard.