Marquer le trafic d'un cgroup est la partie facile. Trois décisions distinctes doivent s'aligner avant qu'une seule application ne sorte par l'interface physique tandis que tout le reste reste dans le tunnel : quel socket appartient à l'exception, quelle recherche de routage la marque déclenche, et quelle adresse source le paquet porte lorsqu'il sort. La plupart des tentatives ratées réussissent la première, réussissent à moitié la deuxième et ne regardent jamais la troisième.

Trois décisions, pas une

La classification répond qui est exempté. Le routage répond où va le paquet marqué à la place. La sélection de la source répond ce que verra l'extrémité distante et où la réponse doit revenir.

Ces étapes échouent indépendamment. Un paquet peut être classifié, rerouté vers l'interface physique et pourtant porter une adresse privée qu'aucun routeur en amont ne peut renvoyer, ce qui produit un délai d'attente qui ressemble exactement à un blocage de pare-feu. Rien dans l'étape de classification ne vous en avertit. Rien dans l'étape de routage non plus.

L'ordre compte aussi. La classification a lieu quand le paquet est généré, le routage après cela, et la traduction au point de sortie.

Si vous ne pouvez pas nommer laquelle des trois étapes est cassée, vous ne déboguez pas, vous réordonnez des règles en espérant.

Ciblez un cgroup qui existera encore demain

nftables peut tester l'appartenance d'un socket à cgroup v2, ce qui est vraiment utile parce que cela suit le processus plutôt que la destination. Le piège, c'est l'identifiant. Une application de bureau lancée depuis un menu vit souvent dans un scope transitoire dont le nom change au lancement suivant.

nft list ruleset
systemctl status session.slice 2>/dev/null
cat /proc/self/cgroup

Copier le nom d'un scope depuis une session et le coder en dur produit une règle qui fonctionne aujourd'hui et cesse silencieusement de correspondre demain. Placez plutôt l'application dans un contexte d'exécution délibéré et stable, comme un scope ou une unit dédiée que vous contrôlez, et ciblez-le. La règle survit alors aux redémarrages et aux mises à jour de paquets. Elle devient aussi quelque chose sur quoi un collègue peut raisonner sans l'historique de vos sessions.

Soyez aussi précis sur le mécanisme que vous utilisez. La classification net_cls de cgroup v1 et la correspondance de socket de cgroup v2 sont des choses différentes, et un champ au nom similaire dans la documentation ne les rend pas interchangeables.

C'est une chaîne de type route qui déclenche la seconde recherche

Pour le trafic généré localement, le type de chaîne décide si quelque chose se produit après la pose de la marque. Une chaîne de sortie de type filter peut marquer un paquet et le noyau continuera d'utiliser la route qu'il a déjà choisie. Une chaîne de type route est le mécanisme qui demande une nouvelle recherche.

table inet bypass {
  chain mark {
    type route hook output priority mangle; policy accept;
    socket cgroupv2 level 2 "app.slice" meta mark set 0x1 comment "app bypass"
  }
}
ip rule add fwmark 0x1 lookup 200
ip route add default via 203.0.113.1 dev wlan0 table 200

La marque a ensuite besoin d'une règle de politique qui l'envoie vers une table, et cette table doit être complète : une route par défaut, toutes les destinations locales dont l'application a encore besoin, et une passerelle en amont qui répond réellement.

Choisissez la valeur de la marque délibérément. WireGuard utilise son propre fwmark pour le trafic de transport extérieur, et tomber par accident sur le même nombre peut perturber l'exception de routage du tunnel lui-même d'une manière pénible à diagnostiquer.

L'adresse source ne suit pas la route

Un socket qui a déjà choisi une adresse source pour le chemin du tunnel conserve cette adresse après le reroutage. Rien dans le changement de routage ne la réécrit, c'est pourquoi le paquet quitte l'interface physique avec une source privée que le réseau amont ne peut pas renvoyer.

Le symptôme visible est une application qui semble envoyer des données et ne reçoit jamais de réponse, et un journal de pare-feu qui n'affiche rien parce que le paquet n'est jamais revenu.

La correction étroite est une règle de traduction de source limitée à la marque de cette application et à sa véritable interface de sortie, intégrée au NAT existant plutôt que de le remplacer :

table inet bypass {
  chain postrouting {
    type nat hook postrouting priority srcnat; policy accept;
    meta mark 0x1 oifname "wlan0" masquerade
  }
}

Traduisez l'exception et rien d'autre. Une règle masquerade globale corrige le symptôme et détruit votre capacité à dire quel trafic a pris quel chemin. Ciblez la marque, la véritable interface de sortie et, si possible, l'adresse propre de l'application. Les règles larges sont faciles à écrire et presque impossibles à auditer ensuite.

Ce qui échappe encore à l'exception

La résolution de noms peut venir d'un processus resolver distinct dans un autre cgroup, donc classifier le socket du navigateur ne classifie pas ses requêtes. Si les requêtes du resolver restent dans le tunnel alors que la connexion en sort, les réponses split-horizon peuvent ne pas correspondre au chemin. Cette divergence produit des pannes qui ressemblent à des bugs DNS et n'en sont pas.

Les processus enfants héritent du cgroup dans la plupart des configurations, mais un assistant qui se déplace dans son propre scope ne le fait pas. Vérifiez au lieu de supposer, surtout pour tout ce qui se réexécute.

Enfin, décidez ce qui doit se passer lorsque le tunnel est indisponible. Une exception qui devient silencieusement un contournement complet est une posture de sécurité différente d'une exception qui échoue fermée, et une seule des deux est ce que vous vouliez.

Articles similaires