Routage du trafic de l'hôte via un VPN dans une VM : six contrôles
Six contrôles ordonnés pour transformer une VM en passerelle VPN par laquelle son propre hôte route son trafic : mode d'adaptateur, transfert, NAT, routes de l'hôte, DNS, MTU.
Six contrôles, exécutés dans l'ordre, décident si une VM peut servir de passerelle VPN à son propre hôte. L'hyperviseur ne fait rien de tout cela pour vous : l'invité possède le tunnel, l'hôte possède une route, et le réseau entre eux n'est que du routage ordinaire. Je garde cette liste car sauter une étape produit toujours le même symptôme, le silence, où l'hôte pingue joyeusement l'invité sans atteindre quoi que ce soit d'interne.
Commencez par la raison. Soit le client d'entreprise ne s'installe que dans l'invité, soit la politique l'y maintient, et vous avez toujours besoin du serveur de build interne depuis l'hôte. Si l'hôte a droit à son propre client, faites cela à la place : un seul tunnel sur l'hôte l'emporte toujours sur deux réseaux plus un NAT.
Contrôle 1 : l'hôte a besoin d'une liaison qu'il peut adresser
L'hôte doit posséder une interface dans le même préfixe que le second adaptateur de l'invité. Host-only dans VirtualBox, host-only ou un VMnet personnalisé dans VMware Workstation, un pont interne ou un périphérique tap dans QEMU/KVM, et un commutateur Internal dans Hyper-V conviennent tous. Invité 192.168.56.2, hôte 192.168.56.1, préfixe 192.168.56.0/24 est la disposition que j'utilise en laboratoire.
Le réseau QEMU en mode utilisateur (-netdev user, le backend SLIRP par défaut) et le NAT de VirtualBox échouent complètement à ce contrôle. Les paquets de l'invité atteignent un processus mandataire sur l'hôte, si bien que l'hôte ne voit jamais d'équipement portant les adresses de l'invité et n'a rien vers quoi pointer une route. Le Default Switch de Hyper-V est la même histoire. Si ping 192.168.56.2 depuis l'hôte échoue, arrêtez-vous : aucune route ajoutée plus tard n'y remédiera.
Contrôle 2 : l'invité transfère et traduit
Les paquets venant de l'hôte arrivent avec une adresse source host-only et une destination interne, donc l'invité doit les transférer puis les cacher derrière l'adresse de son tunnel. Sur un invité Linux, cela signifie le transfert, une paire FORWARD accept et du source NAT vers le tunnel :
sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -A FORWARD -i eth1 -o tun0 -j ACCEPT
sudo iptables -A FORWARD -i tun0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -t nat -A POSTROUTING -s 192.168.56.0/24 -o tun0 -j MASQUERADE
La règle MASQUERADE est la partie que les gens oublient. Les routeurs d'entreprise savent rarement que 192.168.56.0/24 existe ; avec la règle, les serveurs internes voient l'adresse de tunnel de l'invité et y répondent, et la réponse retrouve son chemin vers l'invité.
Un invité Windows est plus maladroit. IPEnableRouter=1 sous SYSTEM\CurrentControlSet\Services\Tcpip\Parameters active le transfert sans traduction, donc cela ne marche que là où le routage d'entreprise pointe déjà vers votre préfixe host-only. Le partage de connexion Internet traduit, mais il renumérote l'adaptateur partagé en 192.168.137.1, alors lisez l'adresse qu'il a réellement prise avant d'écrire des routes sur l'hôte.
Contrôle 3 : les routes de l'hôte nomment l'invité, pas le tunnel
Faites passer par l'invité uniquement les préfixes dont l'hôte a besoin. Sous Linux, une route par préfixe suffit et se retire proprement :
sudo ip route add 10.20.0.0/16 via 192.168.56.2
ip route get 10.20.30.40
Sous Windows, l'équivalent est route add 10.20.0.0 mask 255.255.0.0 192.168.56.2. Laissez -p de côté pendant les tests, pour qu'une mauvaise route meure au redémarrage, et ajoutez-le une fois le chemin fonctionnel. Ne supprimez pas la route par défaut de l'hôte sauf si toutes les applications de l'hôte doivent utiliser le tunnel ; si le tunnel complet est vraiment l'exigence, ajoutez les deux routes /1 et gardez la route directe vers le réseau de l'hyperviseur :
sudo ip route add 0.0.0.0/1 via 192.168.56.2
sudo ip route add 128.0.0.0/1 via 192.168.56.2
Sur un hôte Windows, vérifiez aussi la métrique de l'interface. Une route ajoutée alors que l'adaptateur host-only était désactivé reste dans la table mais ne transporte jamais de trafic, et un second adaptateur de métrique plus bas peut gagner la recherche pour le même préfixe.
Contrôle 4 : le DNS suit la route, pas le défaut du tunnel
Les routes déplacent les paquets ; le résolveur décide quels noms produisent des paquets. Limitez le résolveur d'entreprise à la zone d'entreprise au lieu de remplacer le résolveur de l'hôte, et pointez-le vers un serveur DNS que la nouvelle route atteint déjà. Avec systemd-resolved sur l'hôte :
resolvectl dns eth1 10.20.0.53
resolvectl domain eth1 '~corp.example.com'
Sous Windows, une entrée de la table de stratégie de résolution de noms fait le même travail : Add-DnsClientNrptRule -Namespace ".corp.example.com" -NameServers 10.20.0.53. Si l'invité fait tourner son propre résolveur sur l'adresse host-only, utilisez cette adresse à la place, mais seulement après avoir confirmé qu'il répond sur 192.168.56.2 et non sur loopback.
Vérifiez ensuite le MTU avant d'accuser le DNS de lenteur. L'interface du tunnel est plus petite que l'interface 1500 octets de l'hôte, donc un hôte qui envoie des segments pleine taille avec le bit DF activé les voit tomber quelque part dans l'invité. Le symptôme classique est un ping qui fonctionne et une application web interne qui se bloque après la poignée de main TLS. Serrez l'MSS sur le chemin de transfert de l'invité :
sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
Contrôle 5 : vérifiez des deux extrémités
Depuis l'hôte, traceroute ou tracert vers une adresse interne doit montrer 192.168.56.2 comme premier saut, pas la passerelle physique. Dans l'invité, regardez la même tentative sortir : tcpdump -ni any host 10.20.30.40 montre la requête transférée côté host-only et la requête traduite sur le tunnel. Une requête qui sort, revient et laisse malgré tout l'hôte muet désigne le chemin de retour.
Deux défaillances expliquent l'essentiel. Soit le tunnel de l'invité ne porte aucune route de retour pour le préfixe host-only parce que la règle NAT manque, soit la route de l'hôte pointe vers une adresse que l'invité ne détient plus après un redémarrage. Relisez les adresses avec ip -br addr et ip route get au lieu de vous fier aux notes de la session précédente.
Contrôle 6 : faites survivre au redémarrage et à une mise à jour de l'hyperviseur
Rendez les deux moitiés persistantes : configuration de routes netplan ou NetworkManager sur l'hôte, route add ... -p sous Windows, et net.ipv4.ip_forward=1 dans /etc/sysctl.d/ plus un jeu de règles de pare-feu qui se recharge dans l'invité. Prenez un instantané de l'invité dès que la configuration est vérifiée, avant que la prochaine mise à jour du client d'entreprise n'arrive.
Les réseaux host-only sont renumérotés quand un hyperviseur recrée ses adaptateurs, ce qui invalide silencieusement chaque route et règle NAT bâtie sur 192.168.56.0/24. Après une mise à jour, rejouez les trois premiers contrôles dans l'ordre ; cela coûte une minute et attrape le cas où l'hôte a encore sa route alors que l'invité est passé sur un nouveau sous-réseau.
IPv6 demande son propre passage. Les règles ip6tables, les enregistrements AAAA et une seconde famille de routes de l'hôte ne suivent pas la configuration IPv4, et un service d'entreprise joignable en IPv6 paraîtra durablement cassé alors qu'IPv4 fonctionne.
Réussi ou non, dans l'ordre : l'hôte peut pinguer l'adresse host-only de l'invité ; l'invité transfère et fait MASQUERADE vers le tunnel ; ip route get renvoie l'invité comme passerelle pour le préfixe interne ; les noms d'entreprise se résolvent via le résolveur limité ; traceroute montre l'invité en premier ; et les mêmes six réponses survivent à un redémarrage.