Quelqu'un demande que le navigateur utilise le tunnel, et la réponse revient sous la forme d'une règle de pare-feu correspondant aux ports de destination 80 et 443. Elle paraît précise et ne l'est pas. Une correspondance de port sélectionne les paquets d'après leur en-tête de transport, pas d'après le programme qui a ouvert la socket, et la navigation moderne ne se limite pas à ces deux ports sur TCP. La règle manquera une partie du trafic que vous vouliez inclure et capturera un trafic que vous ne comptiez jamais toucher. Avant de l'écrire, décidez laquelle des trois frontières différentes vous voulez réellement.

La règle décrit une classe de transport, pas une application

Sous Linux, une correspondance de port de destination inspecte l'en-tête TCP et rien au-dessus. Marquer ces paquets et les diriger avec une route de politique fonctionne exactement comme documenté, et aucune de ces mécaniques ne sait ce que la charge utile signifie ni quel processus l'a envoyée. La marque est un nombre attaché à un paquet, pas une identité attachée à une application.

nft add rule inet mangle output tcp dport { 80, 443 } meta mark set 0x1
ip rule add fwmark 0x1 table 100
ip route add default dev wg0 table 100

Deux conséquences suivent immédiatement. Un outil de mise à jour qui contacte TCP 443 reçoit le même traitement que le navigateur, ce qui peut convenir ou bien être une panne qui attend le prochain cycle de mise à jour. Et un service web publié délibérément sur un autre port, disons 8443, tombe entièrement hors de la règle alors qu'il reste le trafic web qui importait à l'utilisateur.

Les nombres sont des sélecteurs légitimes quand l'exigence porte réellement sur ces ports. Ils deviennent une fiction quand le ticket dit « navigateur » et que le jeu de règles implémente une plage de ports. Cet écart n'est pas une coquetterie documentaire. C'est la différence entre une règle que vous pouvez défendre en revue et une que personne ne peut tester.

HTTP/3 abandonne discrètement votre règle TCP

HTTP/3 passe par QUIC, et QUIC est transporté en UDP. Une règle qui ne correspond qu'à TCP ne peut pas le voir, donc le même navigateur qui satisfait votre page de test peut envoyer une requête ultérieure en UDP et contourner le tunnel sans que personne ne change un réglage. RFC 9114 décrit HTTP/3 comme un protocole fondé sur QUIC, et QUIC utilise UDP avec un port par défaut de 443 dans le déploiement courant.

Qu'un navigateur donné tente HTTP/3 dépend de sa configuration, du site et de la réussite d'une tentative précédente. Cela en fait une mauvaise chose à raisonner de mémoire. Demandez plutôt à l'outil :

curl --http3 -sS -o /dev/null -w '%{http_version} %{time_total}\n' https://example.com
ss -tunp | grep -E ':443\b'

La ligne curl affiche la version négociée, donc un 3 confirme que le chemin UDP a été utilisé. La liste des sockets montre ensuite si cette connexion est liée à l'interface du tunnel ou à la physique. Si HTTP/3 n'est pas disponible dans votre build de curl, l'option échoue bruyamment au lieu de se rabattre silencieusement, ce qui est sa petite miséricorde.

Le DNS est une décision distincte que vous devez prendre

Une règle de port pour le trafic web ne dit rien sur la résolution de noms. Le navigateur peut utiliser le résolveur du système, un résolveur configuré dans le navigateur ou celui qu'a poussé la dernière connexion. Chacun d'eux peut envoyer des requêtes par un chemin différent de celui que sélectionne votre règle, ce qui produit la scission classique : la page se charge, le nom se résout quelque part que vous n'attendiez pas, et le site interne se résout vers une adresse publique ou pas du tout.

Décidez-le explicitement. Si les noms internes doivent se résoudre par le tunnel, le résolveur doit être à l'intérieur, et le jeu de règles doit autoriser le DNS vers ce résolveur tout en gardant les autres requêtes dehors. Si le navigateur utilise DNS over HTTPS vers un fournisseur public, la requête contourne entièrement votre résolveur et la règle de port ne peut pas aider. Écrivez le choix dans le même enregistrement de changement que la règle de routage, car les deux sont lus ensemble par celui qui déboguera la prochaine panne.

Vérifiez ensuite avec le résolveur plutôt qu'avec la page :

resolvectl query intranet.example.internal
resolvectl status | grep -A3 'Current DNS Server'

Une réponse absente est un problème de résolution même quand le message d'erreur du navigateur accuse le réseau. Ces deux défaillances paraissent identiques à l'utilisateur et sont des tickets complètement différents.

Trois frontières, trois mécanismes différents

L'essentiel de la confusion disparaît dès que vous nommez la frontière que vous voulez. Elles ne sont pas interchangeables, et chacune a un mode de défaillance différent lors d'une coupure du tunnel.

What you actually wantMechanism that fitsWhat it does not cover
These transport ports use this exitFirewall mark plus policy routeApplications, HTTP/3 over UDP, DNS
This browsing session stays separateApplication-aware VPN, or a separate network namespace plus a proxyOther applications in the same session
Only these private sites are reachableDestination-based routes with private DNSPublic sites on the same address family

Un profil de navigateur séparé est le piège de la ligne du milieu. Il sépare les cookies et l'historique, et il ne change rien à la table de routage de l'hôte, donc les deux profils prennent le même chemin. L'isolation exige quelque chose que le noyau impose, c'est-à-dire un espace de noms, un conteneur ou une application qui possède sa propre interface de tunnel.

Quelle que soit la ligne applicable, écrivez ce qui doit se passer quand le tunnel tombe. Une route fondée sur une marque qui disparaît avec l'interface retombe généralement sur la route par défaut physique, ce qui est un comportement fail-open. Cela peut être acceptable pour la navigation et inacceptable pour tout le reste, et c'est une décision, pas un détail d'implémentation.

Testez les flux que vous prétendez couvrir

Le test d'acceptation doit suivre la frontière que vous avez écrite, pas les numéros de port qui étaient commodes. Choisissez un flux qui doit passer par le tunnel, un qui ne doit pas, et un qui utilise un transport auquel vous n'avez pas pensé :

ip route get 203.0.113.40
curl -sS -o /dev/null -w 'v4 %{http_version} %{remote_ip}\n' https://example.com
nft list ruleset | grep -A2 'dport'

La recherche de route vous dit quelle interface une destination utilise réellement, et c'est le fait que la plupart des rapports se trompent. La ligne curl confirme la version négociée et la famille d'adresses. Le vidage du jeu de règles attrape la règle que vous avez ajoutée pendant les tests et oublié de délimiter, et c'est ainsi qu'une marque temporaire devient un routage fail-open permanent.

Enregistrez les trois sorties avec un horodatage et conservez-les avec le changement. Quand quelqu'un signalera plus tard que « le VPN a cassé l'intranet », l'écart entre la frontière prévue et celle implémentée sera visible dans cet enregistrement, et le correctif sera un changement de règle et non une discussion.

Articles similaires