Chaîner deux clients OpenVPN sur un seul hôte Linux avec fwmark
Les deux clients veulent la route par défaut. Épinglez le serveur interne avec une route d'hôte, empêchez le second client de réécrire les tables, puis pilotez les flux avec fwmark.
Empiler un second client OpenVPN sur un seul hôte Linux échoue à l'un de deux endroits. Soit le client interne n'atteint pas son propre serveur parce que le tunnel externe a capté ce transport, soit le second client réécrit la table de routage et le premier tunnel cesse de fonctionner. Les deux problèmes relèvent du routage, non du chiffrement, et les deux se règlent avant que le nouveau tunnel ne transporte du trafic applicatif. Les adresses ici sont illustratives : tun0 est le tunnel externe, 203.0.113.9 est le serveur interne et TCP 9999 est le flux qui doit emprunter le chemin interne.
Deux modes de défaillance, une table de routage
Un client interne ouvre une socket UDP vers son serveur, et cette socket suit la table de routage courante comme n'importe quelle autre. Si le tunnel externe possède déjà la route par défaut, les paquets de la poignée de main entrent dans le tunnel externe. Cela ne fonctionne que si le serveur externe peut joindre le serveur interne et que l'autre extrémité accepte de porter le flux ; sinon la poignée de main se bloque et le client réessaie.
La seconde défaillance vient du push du serveur. Le push habituel est redirect-gateway def1, qui installe 0.0.0.0/1 et 128.0.0.0/1 au lieu de remplacer la route par défaut. Deux clients qui installent chacun leur propre paire /1 entrent donc en collision, et la paire la plus récente l'emporte pour presque toute destination. Cherchez ces deux préfixes dans la table quand un tunnel meurt après la connexion du second, car la route par défaut elle-même n'a peut-être jamais changé.
Marquez le flux et donnez-lui une table
Orientez le trafic choisi en le marquant dans la table mangle et en faisant correspondre la marque dans une règle de politique. La règle pointe vers une table dédiée dont la route par défaut est le périphérique interne, de sorte que seuls les paquets marqués prennent le chemin interne.
iptables -t mangle -A PREROUTING -p tcp -m multiport --dport 9999 -j MARK --set-mark 0x9999
ip rule add fwmark 0x9999 table 200 pref 1000
ip route add default dev tun1 table 200La plupart des défaillances de cet ensemble de règles viennent de deux erreurs. La valeur de marque posée par le pare-feu et la valeur comparée par la règle doivent être identiques, car une divergence ne produit ni erreur ni effet. Et PREROUTING ne voit que les paquets qui arrivent de l'extérieur, donc une connexion lancée sur cet hôte a besoin d'une règle OUTPUT avec la même correspondance avant même d'être marquée.
Retirez la propriété des routes au second client
Deux options empêchent le second client de modifier la table, et elles agissent à des endroits différents. route-nopull ignore les options de route poussées, donc rien n'est installé. route-noexec accepte les options poussées dans l'environnement du client mais laisse la modification de la table à un hook, ce qui est exactement ce que vous voulez quand vous devez voir ce que le serveur a proposé.
Le hook s'exécute avec les valeurs poussées dans son environnement, et il ne devrait ajouter que les entrées que vous avez approuvées. Rendez-le idempotent : videz la table qu'il possède avant de la reconstruire, et ne supposez pas que l'exécution précédente a laissé un état propre. Une reconnexion rappelle le hook, et un hook qui ajoute sans nettoyer accumulera des routes en double.
Choisissez un seul propriétaire et écrivez-le. Un profil qui définit les deux options, ou un hook qui concurrence un script manuel, produit une table que personne ne pourra expliquer plus tard.
Épinglez les deux adresses des serveurs
Le serveur interne a besoin d'une route d'hôte via le périphérique externe avant que le client interne ne se connecte. C'est cette entrée qui maintient le transport interne dans le tunnel externe au lieu de le laisser suivre la route par défaut.
ip route add 203.0.113.9/32 dev tun0
ip route get 203.0.113.9Ajoutez aussi une route d'hôte correspondante pour le serveur externe dans la table principale, sinon une reconnexion peut router le transport du client externe lui-même dans son propre tunnel. L'ordre n'est pas facultatif : ajoutez ces entrées après que l'interface concernée existe, c'est-à-dire via un hook ou un script exécuté après la connexion. Une route pointant vers un périphérique hors service est supprimée ou ignorée, donc une entrée écrite au démarrage ne survit pas à la séquence que vous avez supposée.
Vérifiez la recherche au lieu de lire toute la table. La sortie de la recherche de route nomme le périphérique et l'adresse source que le noyau utilisera, ce qui est exactement ce que le client obtient quand il ouvre sa socket.
Testez la chaîne et rendez la défaillance visible
Capturez sur l'interface externe pendant que le client interne se connecte. La poignée de main interne devrait apparaître comme une charge chiffrée dans le tunnel externe ; si elle apparaît comme de l'UDP simple vers 203.0.113.9, la route d'hôte manque et la chaîne n'a jamais été construite.
Une chaîne qui retombe silencieusement sur le lien physique est le pire résultat, car l'état de l'interface n'annonce rien et le trafic interne part de la mauvaise adresse source. Si le second client peut se reconnecter directement quand le tunnel externe tombe, désactivez ce chemin ou liez-le à l'interface externe pour que la défaillance soit visible.
Terminez avec deux flux et un vidage de règles : une connexion marquée et une non marquée devraient prendre des chemins différents, et ip rule show devrait lister la préférence que vous vouliez. Si les deux flux prennent le même chemin, la marque ne s'applique pas là où vous pensez.