Pourquoi votre règle iptables ne voit jamais le trafic VPN ponté
Les trames pontées peuvent contourner la chaîne IP que vous surveillez. Comparez la famille bridge native au br_netfilter historique avant d’ajouter une autre règle.
Deux machines virtuelles sur le même hôte atteignent le LAN via un pont, et une règle placée dans la chaîne ip forward affiche un compteur qui ne bouge jamais. Les trames sont réelles, le trafic passe, et la règle n’est tout simplement pas sur son chemin. Comparer les deux façons dont Linux expose le trafic ponté explique ce silence, et explique aussi pourquoi charger un module hérité est une réponse pire qu’elle n’en a l’air.
Deux chemins à travers un même hôte
Un paquet peut quitter cet hôte de deux manières très différentes, et la différence décide quels hooks le voient. Si la destination se trouve au-delà d’un port physique et que la trame est commutée entre deux ports du pont, l’hôte transmet une trame Ethernet ; le code de routage n’est pas consulté, et aucun saut routé n’a lieu. Si la destination est l’hôte lui-même, ou un réseau atteint par une interface tunnel, le paquet est routé et les hooks de la couche IP s’appliquent bien. Gardez cette bifurcation en tête, car chaque question ultérieure sur les chaînes et les familles découle du chemin que prend une trame.
Cette distinction est toute l’histoire derrière une règle qui ne compte rien. Une trame pontée de veth9a1 vers enp3s0 traverse le pont, pas la pile IP, donc une chaîne ip forward ne voit rien à comparer. Les noms d’interface d’un exemple plus ancien ne déterminent pas le chemin que vous avez, et c’est pourquoi la première étape est de regarder la topologie plutôt que le jeu de règles.
Un tunnel ajoute un troisième cas qui mérite un nom. Un périphérique TUN remet des paquets IP à l’hôte, donc ceux-ci sont routés et traversent bien les chaînes IP, tandis qu’un périphérique TAP attaché à un pont remet des trames et se comporte comme le cas ponté. C’est le type de périphérique, et non le nom du produit, qui décide.
Comparez les deux familles qui peuvent voir une trame pontée
Linux prend en charge le filtrage de pont natif via la famille bridge de nftables, et il n’a besoin d’aucun module hérité pour inspecter et rejeter des trames en vol. La documentation du pont du noyau lui-même fait cette recommandation directement, ce qui vaut la peine d’être su avant qu’un résultat de recherche vous dise de charger un module.
| Observation point | What it sees | What it costs |
|---|---|---|
nftables bridge family chains | Ethernet frames forwarded between bridge ports, in both directions | Rules must be written in bridge syntax with link-layer matches |
Legacy br_netfilter plus an ip chain | Bridged IPv4 and IPv6 packets, re-exposed through IP-layer hooks | Existing host rules may begin matching traffic they never saw before |
inet chain for routed traffic | Packets the host routes, including traffic arriving from a TUN device | No visibility into purely switched frames |
Utiliser la commande nft au lieu d’iptables ne place pas, à lui seul, une règle dans la bonne famille. Une chaîne déclarée dans la famille bridge voit les trames ; une chaîne déclarée dans ip, ip6 ou inet ne les voit pas, quel que soit le binaire qui l’a créée. La famille est une propriété de la chaîne, non de l’outil qui l’a tapée.
Le chemin hérité fonctionne différemment, et c’est pourquoi il faut comparer plutôt que choisir un camp par habitude. Charger br_netfilter et activer les réglages bridge-nf-call réexpose le trafic IPv4 et IPv6 ponté à travers les hooks de la couche IP, de sorte qu’une règle iptables ordinaire peut enfin y correspondre. Les trames ne changent pas ; l’ensemble des hooks qu’elles traversent change, ce qui signifie que chaque règle d’hôte existante a désormais un nouveau public.
Lisez le chemin effectif avant d’ajouter une règle
Trois commandes établissent où vous en êtes : ce à quoi le pont est attaché, si le module hérité est actif, et quelles chaînes existent déjà dans la famille bridge. Exécutez-les avant d’écrire quoi que ce soit, car la réponse décide lequel des deux designs vous étendez. Sauter cette étape est la façon dont une règle correcte finit dans une chaîne que rien ne traverse.
bridge link show
lsmod | grep br_netfilter
nft list ruleset | grep -A4 "chain forward"
La sortie sépare proprement les cas. Les ports listés sous le pont vous disent quelles interfaces sont commutées ; un résultat lsmod vide signifie aucune visibilité au niveau IP des trames pontées ; une chaîne forward existante de la famille bridge signifie qu’un propriétaire de politique existe déjà et que votre règle appartient là plutôt que dans une nouvelle chaîne à elle.
Ajoutez ensuite une règle facile à relire, avec un compteur et sans changer le verdict, et faites passer un seul flux représentatif par le pont. Un compteur qui bouge prouve que la trame a atteint la règle. C’est tout ce qu’il prouve, alors suivez-le aussi avec le chemin refusé, puisqu’une politique qui accepte tout produit elle aussi un compteur qui bouge.
nft add rule bridge filter forward iifname "veth9a1" oifname "enp3s0" counter acceptOù le module hérité change le comportement
Activer le chemin hérité sur un pont en fonctionnement n’est pas un acte neutre d’ajout de visibilité. Les règles auparavant inaccessibles au trafic ponté le deviennent, y compris les règles d’hôte écrites pour une autre interface, et une politique drop par défaut dans la chaîne ip forward peut soudain s’appliquer à des trames qui passaient intactes. Sur un hôte administré à travers ce pont, le résultat est un verrouillage sans console. Ce n’est pas un mode de défaillance hypothétique ; c’est la conséquence ordinaire d’élargir un ensemble de hooks sur une machine à laquelle vous accédez par le chemin que vous venez d’élargir.
La séquence la plus sûre est de décider quelle famille possède la politique, d’y écrire les règles, et de conserver un chemin de récupération indépendant avant de modifier le moindre sysctl. Si deux familles détiennent des règles, documentez laquelle fait autorité ; deux politiques qui se chevauchent sont la façon dont un paquet est accepté à un endroit et rejeté à un autre sans un seul fichier à lire.
L’isolement lui-même demande encore une conception complète. Autoriser une source vers une destination ne prouve qu’un flux fonctionne, alors que l’exigence est généralement qu’une VM ne puisse atteindre ni ses voisins ni l’hôte. Les compteurs des règles de refus importent plus que le compteur de la règle d’acceptation, car le compteur d’acceptation est celui dont vous vous attendiez déjà à ce qu’il bouge.
Enfin, gardez le cas de l’entrée de l’hôte séparé. Le trafic adressé à l’IP propre du pont arrive sur input, pas sur forward, et une règle dans forward ne le verra jamais, quelle que soit la famille choisie. Trois cas, deux familles, une leçon : le chemin décide du hook, et le hook décide si votre règle existe tout court.