La demande est toujours une variante de la même phrase : garder le VPN pour la navigation, mais laisser SSH ou une tâche de sauvegarde utiliser la connexion normale. Le conseil qui circule à ce sujet, réacheminer le port avec des règles NAT d'iptables, ne le fait pas. Une règle NAT réécrit les adresses et les ports à l'intérieur d'un flux ; elle ne choisit jamais par quelle interface le paquet sort. Cette décision relève de la recherche de route, et la recherche de route a besoin d'aide pour voir un port.

Pourquoi une route ordinaire ne peut pas correspondre à un port

Linux choisit une route en cherchant l'adresse de destination dans une table, après avoir filtré les tables candidates par des règles de politique. Ces règles peuvent correspondre à une marque de pare-feu, une adresse source, une interface d'entrée ou un UID. L'entrée de route elle-même n'a pas de champ de port, car la route est choisie avant que l'en-tête de transport compte.

Les iproute2 et les noyaux actuels élargissent le sélecteur de règle : ipproto, sport et dport sont acceptés, donc ip rule add ipproto tcp dport 22 table 100 exprime l'intention en une ligne. Exécutez ip rule add help sur l'hôte réel pour voir si votre build liste dport. Si oui, vous avez fini en une commande ; sinon, le chemin par marque ci-dessous est l'équivalent.

Quoi qu'il en soit, la règle n'est qu'un pointeur. La table 100 a toujours besoin d'une route, et c'est la pièce que les gens oublient.

Trois pièces, ou une

La forme moderne est la règle unique ci-dessus plus une route dans la table alternative. La forme classique ajoute netfilter au milieu : marquer le paquet, puis faire correspondre la marque dans une règle. Les deux aboutissent à la même recherche ; elles diffèrent par l'endroit où le port est lu.

L'approche par marque existe parce que netfilter voit l'en-tête de transport et que le code de routage ne le voyait pas historiquement. C'est tout le mécanisme, et cela explique pourquoi le repli est trois pièces qui coopèrent plutôt qu'une commande astucieuse.

Les trois pièces dans l'ordre

D'abord, marquez les flux que vous voulez détourner, dans une chaîne qui s'exécute assez tôt pour que la marque existe avant la décision de route :

nft add table inet vpnexempt
nft 'add chain inet vpnexempt out { type route hook output priority mangle; }'
nft add rule inet vpnexempt out tcp dport 22 meta mark set 0x1

Ensuite, envoyez les paquets marqués vers une table distincte :

ip rule add fwmark 0x1/0x1 table 100

Troisièmement, donnez à cette table une route par défaut via le lien physique. Les adresses sont illustratives ; remplacez-les par les vôtres :

ip route add default via 192.168.1.1 dev wlan0 table 100

Sautez la troisième étape et le paquet marqué ne trouve aucune route dans la table 100, donc il échoue. Le symptôme ressemble à une règle qui a cassé le réseau, alors qu'elle a en fait supprimé le seul chemin qui restait au paquet.

Ce qui le maintient en marche après le premier paquet

Les marques s'appliquent aux paquets sortants. La réponse arrive sur l'interface physique avec l'adresse que ce lien a attribuée, et la table principale peut encore préférer le tunnel pour cette destination. Deux mécanismes maintiennent le chemin de retour cohérent : une règle basée sur la source pour l'adresse du lien, et le suivi de connexions qui conserve la décision pendant toute la durée de vie du flux. Enregistrer la marque dans conntrack et la restaurer sur la réponse est la manière habituelle de rendre stable une exception avec état.

Le filtrage du chemin inverse est l'autre tueur silencieux. Si le noyau valide strictement le chemin de retour et que la réponse arrive sur une interface que la table principale ne choisirait pas, le paquet est supprimé sans message. rp_filter les valeurs par défaut varient selon la distribution et selon que l'interface est transférée, alors lisez la valeur effective au lieu de supposer qu'elle est désactivée.

Ne marquez pas le transport du tunnel lui-même. Un tunnel WireGuard ou UDP OpenVPN n'est pas TCP 22, donc la recette ci-dessus est sûre, mais une exception écrite comme tout sauf la destination du tunnel peut capturer les paquets chiffrés et casser le tunnel d'une manière qui ressemble à une boucle de routage.

Vérifiez au lieu de supposer

Demandez au noyau quel chemin il prendrait, pour la destination et pour le port, puis lancez le transfert réel :

ip route get 203.0.113.10
ip rule show
nft list ruleset

Si la marque existe mais que les compteurs ne bougent jamais, la règle ne correspond pas. Les compteurs sont la différence entre une configuration que vous avez écrite et une politique que le noyau applique.

Quand c'est le mauvais outil

Les exceptions basées sur le port sont fragiles par nature. Les services changent de port, HTTP/3 arrive en UDP 443, et une application peut ouvrir plusieurs flux de types différents. Si l'exigence réelle est que ce programme utilise le lien normal, la couche de routage est le mauvais endroit ; un espace de noms réseau ou un proxy par application convient mieux.

Si l'exigence est que ce sous-réseau utilise le lien normal, omettez complètement les marques et ajoutez une route plus spécifique. C'est plus simple, c'est visible dans une seule table, et la personne suivante peut l'auditer sans lire un jeu de règles.

Écrivez laquelle des trois exigences vous avez réellement avant de toucher au pare-feu. Le mécanisme ci-dessus est précis, et la précision appliquée à la mauvaise couche n'est qu'une panne plus longue.

Articles similaires