Table des matières de l'article :
Chez Managed Server SRL, nous sommes spécialisés depuis plus de 15 ans dans l'ingénierie des systèmes Linux, la gestion d'infrastructures haute performance et le conseil avancé pour les environnements web critiques . Durant cette période, nous avons géré des milliers de serveurs, optimisé des architectures complexes pour des plateformes CMS et e-commerce à fort trafic et relevé quotidiennement tous les défis typiques de l'hébergement professionnel : sécurité, performance et automatisation de l'infrastructure.
Ayant travaillé pendant des années dans l'ingénierie et le conseil en systèmes Linux, comme beaucoup dans ce secteur, nous avons toujours entretenu une relation étroite avec Netfilter, iptables et tout ce qui concerne le filtrage de paquets au niveau du noyau Linux . Le pare-feu du noyau n'est pas qu'un simple outil de sécurité : il constitue souvent un élément essentiel à la stabilité des infrastructures, notamment pour la gestion des services publics exposés à Internet.
Lorsque nous avons commencé le développement de CFM 4 Linux (Centralized Firewall Manager) , notre outil interne de gestion centralisée des règles de pare-feu sur l'ensemble de nos serveurs, le choix du système de filtrage a été l'une des premières décisions architecturales que nous avons dû prendre. Il nous fallait déterminer quelle technologie utiliser pour gérer efficacement un grand nombre de règles réparties sur des dizaines, voire des centaines de machines.
Et cette décision nous a menés là où nous ne l'avions pas prévu.
Cet article présente nos conclusions, car nous pensons qu'elles seront utiles à tous ceux qui évaluent nftables pour des scénarios de règles à volume élevé — en particulier le géoblocage — et parce que « plus moderne » ne signifie pas toujours automatiquement « plus rapide » ou « plus efficace » dans tous les contextes opérationnels.
Le contexte : le géoblocage à grande échelle
CFM gère les politiques de pare-feu réparties sur des dizaines de serveurs au sein d'infrastructures dédiées à l'hébergement, au e-commerce et aux applications web à fort trafic. Dans ces contextes, le pare-feu constitue non seulement une barrière de sécurité, mais aussi un outil opérationnel permettant de contrôler le trafic vers les services exposés. Parmi les fonctionnalités les plus demandées par nos clients figure le géoblocage , qui permet de bloquer (ou d'autoriser) le trafic en fonction du pays d'origine de l'adresse IP.
Le cas d'utilisation le plus fréquent n'est pas tant le blocage sélectif de quelques pays que le modèle inverse : autoriser uniquement certaines zones géographiques et bloquer tout le reste. Cette approche est très prisée des sites et plateformes opérant exclusivement sur des marchés spécifiques, comme l'Europe ou l'Italie, qui souhaitent limiter leur surface d'attaque, le trafic indésirable ou le web scraping massif en provenance d'autres régions du monde.
Lorsqu'on applique une politique de blocage des adresses IP de certains pays , le nombre de préfixes CIDR à bloquer augmente rapidement. En pratique, il est nécessaire d'inclure dans le pare-feu toutes les plages d'adresses IP appartenant aux pays exclus , ce qui représente dans la plupart des cas la grande majorité des adresses IP mondiales.
Concrètement, cela signifie que vous pouvez facilement gérer entre 50 000 et 100 000 préfixes CIDR . Cela inclut toutes les plages d'adresses IP attribuées à 150 pays ou plus qui ne figurent pas sur la liste blanche.
Ce n'est pas un chiffre inventé.
La base de données GeoIP que nous utilisons — DB-IP Lite , intégrée et normalisée avec les données des registres Internet régionaux (RIPE, ARIN, APNIC, LACNIC et AFRINIC) — produit généralement des ensembles de données de cette taille :
-
environ 51 000 préfixes IPv4 pour un scénario « autoriser uniquement l'Europe »
-
environ 44 000 préfixes IPv6 pour le même scénario
Le total est donc de l'ordre de 95 000 préfixes CIDR qui doivent être entrés dans le pare-feu en tant que règles DROP.
Et il ne s'agit là que du cas de base. Dans des scénarios plus restrictifs — par exemple , en n'autorisant qu'un ou deux pays spécifiques — le nombre de préfixes peut encore augmenter, car la liste blanche se réduit et la liste noire s'élargit considérablement.
L'exigence architecturale pour CFM était donc claire dès le départ : appliquer ces règles afin que le pare-feu continue de fonctionner sans dégrader les performances réseau du serveur . Un serveur web moderne peut gérer des dizaines de milliers de connexions par seconde, et toute inefficacité dans le filtrage des paquets risque d'être immédiatement perceptible en termes de latence ou de débit.
Cela semble être une exigence évidente.
Mais, comme nous le verrons dans les sections suivantes, lorsque l'on aborde les dizaines de milliers de règles CIDR , les différences entre les diverses technologies de filtrage de paquets deviennent beaucoup plus apparentes — et pas toujours de la manière attendue.
Le choix des nftables : tout le monde disait que c'était mieux
Lors de la conception de CFM4Linux, nous avons procédé comme toute équipe sérieuse : nous avons effectué des recherches approfondies . Nous avons consulté la documentation officielle, les articles techniques, les benchmarks publiés, les discussions sur les listes de diffusion et les présentations données lors de conférences sur les réseaux Linux.
Et le message qui en ressortait était toujours le même : nftables est l’avenir, iptables est une technologie obsolète.
Il ne s'agissait pas simplement de marketing ou de discours communautaire. Le projet Netfilter a clairement évolué dans cette direction ces dernières années : nftables a été conçu pour remplacer progressivement iptables, en introduisant un modèle plus moderne, plus cohérent et — du moins sur le papier — plus efficace.
Les avantages des nftables que nous avons trouvés cités partout étaient assez convaincants :
-
Des ensembles natifs avec des recherches en O(1) via des tables de hachage ou des arbres rouge-noir, au lieu de chaînes linéaires de règles avec une complexité O(n) typique d'iptables.
-
Opérations atomiques sur des tables et des chaînes entières, évitant les états intermédiaires incohérents lors des mises à jour de règles
-
Syntaxe unifiée pour IPv4 et IPv6 via des tables
inet, qui éliminent la duplication des règles entreiptableseip6tables -
Plus grande flexibilité linguistique , avec la prise en charge des cartes, des concaténations de clés et des structures de données plus avancées.
-
Performances supérieures dans les benchmarks publiés , y compris le benchmark officiel publié par Red Hat
Ce dernier point a joué un rôle important dans notre décision.
Le test de performance publié par Red Hat en 2017 ( Benchmarking nftables — Red Hat Developer ) a clairement montré comment les ensembles de nftables maintenaient des temps de recherche stables quel que soit le nombre d'éléments , grâce à l'utilisation de structures de données plus efficaces que l'analyse séquentielle des règles.
En d’autres termes : l’ajout de 10 éléments ou de 100 000 éléments à un ensemble nftables ne devrait pas modifier de manière significative le temps d’évaluation des règles.
Pour un système comme CFM, qui devait gérer des dizaines de milliers de préfixes IP, cela semblait être la solution idéale.
Nous avons donc mis en œuvre le géoblocage en utilisant ensemble nommé nftables avec le drapeau interval, ce qui permet de représenter efficacement les plages CIDR.
table inet cfm {
set geo_blocked_v4 {
type ipv4_addr
flags interval
elements = { 1.0.0.0/24, 1.0.4.0/22, 1.0.16.0/20, ... }
# ~51.000 prefissi
}
chain cfm_input {
type filter hook input priority 0; policy accept;
ip saddr @geo_blocked_v4 drop comment "CFM:geo-allow-only"
}
}
Conceptuellement, c'était une solution très simple :
-
Tous les réseaux à bloquer étaient contenus dans un seul ensemble.
-
la chaîne
inputcontenu une seule règle -
La mise à jour des données pourrait se faire de manière atomique.
-
Les protocoles IPv4 et IPv6 pourraient être gérés dans la même table.
inet
Le drapeau interval est essentiel dans ce contexte. Sans cela, un ensemble nftables n'accepte que adresses IP exactes, alors que dans notre cas, nous devions représenter blocs CIDR entiers.
Avec flags interval, nftables utilise en interne une structure de données arborescente rouge-noir, implémenté dans le noyau dans le fichier nft_set_rbtree.cCeci est nécessaire car le pare-feu doit pouvoir gérer :
-
plages d'adresses
-
Gamme CIDR
-
chevauchements possibles entre les préfixes
-
recherches basées sur l'inclusion de l'adresse IP dans une plage
Autrement dit, le système n’effectue plus une simple recherche dans une table de hachage, mais une recherche dans un arbre équilibré d’intervalles.
Sur le papier, cependant, il s'agissait d'une structure extrêmement efficace.
Et c'est là que les choses ont commencé à mal tourner.
Le désastre de la production
Le premier déploiement sur un serveur client devant rendre le système de pointage en ligne accessible uniquement depuis l'Italie (Hetzner, CentOS 7, noyau 6.1, nftables) a semblé fonctionner. Les règles ont été chargées, l'ensemble a été initialisé. nft list set inet cfm geo_blocked_v4 Il affichait les 51 000 préfixes. Tout est en ordre.
Nous avons ensuite essayé d'utiliser le serveur.
La connexion SSH est devenue inutilisable. Chaque commande subissait un délai de 5 à 10 secondes. Les pages Web ne se chargeaient pas. Un simple curl https://www.google.com Cela a pris plus de 5 secondes — et la raison était révélatrice : exactement secondes 5.05, qui correspond au délai d'attente par défaut pour la résolution DNS.
Le DNS était défaillant. Pas complètement – les requêtes étaient transmises – mais la latence était si élevée qu'elle entraînait des délais d'attente. Et sans DNS fonctionnel, tout s'effondre.
Le diagnostic
Nous avons isolé le problème méthodiquement :
- Ensemble Geo vidé ne gardant que les chaînes avec
ct state established,related accept→ Réponse DNS instantanée (0.02 s) - Les 51 000 préfixes de l'ensemble ont été rechargés. → DNS à nouveau en panne (plus de 5 secondes)
- J'ai répété le test à plusieurs reprises → résultat déterministe et reproductible
La cause était sans équivoque : l'ensemble nftables avec flags interval et plus de 51 000 éléments ont provoqué une surcharge catastrophique par paquet au niveau du noyau..
Il ne s'agissait pas d'un problème de chargement ou d'espace utilisateur. L'ensemble était correctement chargé dans le noyau. Le problème résidait dans le temps de recherche pour chaque paquet individuel parcourant la chaîne contenant la référence à l'ensemble.
Pourquoi le DNS ? Pourquoi tout ça ?
Il est essentiel de comprendre ce point. Dans Netfilter, lorsqu'un paquet arrive sur une chaîne et correspond à un ensemble avec flags intervalLe noyau doit parcourir l'arbre rouge-noir pour déterminer si l'adresse IP source se situe dans l'une des plages. Avec un arbre rouge-noir de 51 000 nœuds, chaque recherche peut prendre jusqu'à… ~16 comparaisons (log₂ de 51 000), mais avec les complexités supplémentaires liées à la gestion des plages qui se chevauchent, le coût réel est nettement plus élevé.
Le problème ne réside pas dans la recherche unique, mais dans la recherche globale. des millions de recherches par seconde qu'un serveur normal génère. Chaque paquet DNS sortant, chaque accusé de réception TCP, chaque paquet d'une connexion SSH, chaque fragment de page web — chacun de ces éléments doit transiter par cet ensemble. Même les paquets appartenant aux connexions déjà établi (ESTABLISHED,RELATED) faire l'objet d'une recherche si la règle ct state Cela intervient après le match sur le plateau, ou s'ils sont dans des chaînes différentes avec des priorités différentes.
Ce dernier point mérite d'être approfondi. Dans nftables, les chaînes de base avec des priorités différentes sont tous évalués indépendamment. un accept dans une chaîne de priorité -10 (notre chaîne de sécurité) n'empêche pas pour que le paquet soit également évalué dans la chaîne de priorité 0 (où se trouvait l'ensemble de données géographiques). Un seul drop Il est terminal en ce sens qu'il bloque le paquet, mais un accept Dans une chaîne, le paquet ne saute aucune chaîne : il poursuit son chemin à travers toutes les chaînes enregistrées sur le même point d'accès. Cela signifie que même les paquets de bouclage (127.0.0.1), déjà acceptés par la chaîne de sécurité, ont été évalués par rapport à l'ensemble des 51 000 préfixes.
Les rapports de bogues que nous aurions dû lire avant
Après avoir découvert le problème, nous avons cherché confirmation auprès de la communauté et nous l'avons trouvée. Nous n'étions pas seuls.
Bug Netfilter n° 1735 - « L’ajout progressif d’ensembles d’intervalles nftables ralentit et rend l’interface de ligne de commande nft moins réactive à chaque ensemble ajouté. »Publié en janvier 2024, ce rapport décrit comment les ensembles avec flags interval Les performances se dégradent progressivement. La première itération prend 0.12 seconde, la quarantième 1.59 seconde. La consommation de mémoire atteint 180 Mo. Mais ce bug concerne le temps. chargement de séries, et non d'une recherche par paquet — notre problème était encore pire.
Bogue Netfilter n° 1439 — « La mise à jour/le rechargement atomique d’un grand ensemble de données avec nft -f est excessivement lent . » Ce bogue a confirmé que les grands ensembles de données, notamment dans le cas de la géolocalisation IP, étaient impossibles à gérer avec le rechargement atomique. Ce bogue date de juillet 2020 , soit il y a six ans.
Forum OpenWrt : « Nftables s’enraye avec les très grands ensembles » — Les utilisateurs d’OpenWrt avec le noyau 5.15 ont signalé un comportement étrange avec les grands ensembles, notamment des pics de mémoire lors de l’importation.
Forum OpenWrt : « Quelques réflexions sur les performances de nftables » — Une discussion approfondie sur les performances réelles de nftables par rapport aux attentes, plusieurs utilisateurs signalant des problèmes similaires aux nôtres.
Le noyau récent (6.19) a introduit des optimisations dans le backend pipapo (nft_set_pipapo.c) avec le drapeau .abort_skip_removal (code source sur GitHub), mais celles-ci concernent principalement le temps de annulation des éléments, et non de la recherche par paquet.
La solution : le fractionnement de la chaîne avec iptables
Après avoir constaté la nature du problème — l'arbre rouge-noir à intervalles ne peut tout simplement pas gérer plus de 50 000 préfixes sous une charge réseau réelle —, nous avons dû trouver une alternative.
La solution que nous avons adoptée s'appelle le fractionnement de chaîne et est mise en œuvre à l'aide de la bonne vieille commande iptables-restore. Le concept est simple mais efficace.
Comment ça marche ?
Au lieu d'un ensemble monolithique unique, nous regroupons les préfixes CIDR par leur premier octet (le bloc /8 auquel ils appartiennent). Pour chaque /8 contenant au moins un préfixe à bloquer, nous créons une sous-chaîne dédiée :
*filter
:CFM_GEO - [0:0]
:CFM_GEO_01 - [0:0]
:CFM_GEO_02 - [0:0]
:CFM_GEO_05 - [0:0]
...
# Chain principale: jump per /8
-A CFM_GEO -s 1.0.0.0/8 -j CFM_GEO_01
-A CFM_GEO -s 2.0.0.0/8 -j CFM_GEO_02
-A CFM_GEO -s 5.0.0.0/8 -j CFM_GEO_05
...
# Sub-chain per il /8 = 1
-A CFM_GEO_01 -s 1.0.0.0/24 -j DROP -m comment --comment "CFM:geo"
-A CFM_GEO_01 -s 1.0.4.0/22 -j DROP -m comment --comment "CFM:geo"
...
COMMIT
La chaîne principale CFM_GEO ne contient que les règles de saut pour /8 — généralement Règles 100-150 (les 256 plages /8 possibles ne sont pas toutes occupées). Lorsqu'un paquet arrive avec une adresse IP source, par exemple, 1.2.3.4:
- Traversez la chaîne
CFM_GEO: match on1.0.0.0/8→ sauter unCFM_GEO_01 - Croix
CFM_GEO_01~350 règles spécifiques à cela /8 - Si aucune correspondance n'est trouvée, revenez en arrière et passez au /8 suivant.
Évaluations par paquet : ~150 (jump) + ~350 (sous-chaîne) = ~500
Comparativement à 51 000 pour l’ensemble monolithique (soit environ 16 comparaisons dans l’arbre rbtree, multipliées par la complexité de la gestion des plages), le fractionnement de la chaîne réduit en pratique le nombre d’évaluations par paquet d’un facteur d’ environ 100.
Pour IPv6, nous utilisons le même principe mais en regroupant par /16 (les deux premiers octets de l'adresse), car les préfixes IPv6 sont généralement plus larges et moins nombreux.
Les résultats
Après le passage au fractionnement de chaîne :
| Métrique | Intervalle nft défini (51k) | iptables chain-split |
|---|---|---|
| Requête DNS | 5.05 s (délai d'attente dépassé) | 0.02s |
| curl google.com | 5.10s | 0.05s |
| SSH interactif | inutilisable | normale |
| Règles de chargement | ~ 3s | ~ 2s |
Il ne s'agit pas d'une amélioration mineure ou, disons, « cosmétique ». C'est la différence entre un serveur parfaitement fonctionnel et sans latence et un serveur inutilisable.
Prenons également en compte le fait que cette configuration IPTables fonctionnait sur une configuration extrêmement modeste, à savoir 2 vCPU d'une instance Hetzner et seulement 4 Go de RAM, sachant qu'elle comportait également l'interpréteur PHP actif avec WordPress installé, une base de données Percona Server 5.7, Memcache et bien sûr le serveur Web le plus apprécié des administrateurs système du monde entier : NGINX, pour une utilisation totale d'au moins quelques gigaoctets, comme le montre la capture d'écran suivante.
Le paradoxe de la performance
Il y a une profonde ironie dans cette histoire. nftables a été conçu — et est universellement présenté — comme le remplaçant le plus performant d'iptables. Et pour de nombreux cas d'utilisation, c'est le cas. Les ensembles basés sur le hachage (sans flags intervalLa recherche d'adresses IP exactes fonctionne parfaitement. Les opérations atomiques sont élégantes. La syntaxe unifiée IPv4/IPv6 est un vrai plaisir.
Mais le cas d'utilisation spécifique du géoblocage avec des dizaines de milliers de préfixes CIDR — qui est probablement le cas d'utilisation le plus courant pour les très grands ensembles — est précisément là où nftables échoue de façon catastrophique.
La raison est structurelle : ensemble avec flags interval ils utilisent un arbre rouge et noirIl ne s'agit pas d'une table de hachage. Les plages CIDR ne peuvent pas être hachées directement car elles nécessitent des recherches d'inclusion (l'adresse IP 1.2.3.4 appartient-elle à la plage 1.2.0.0/16 ?), et ce type de recherche requiert une structure de données ordonnée. L'arbre rouge-noir garantit une complexité de O(log n) par opération, mais avec 51 000 éléments et le trafic réseau d'un serveur de production, ce logarithme se multiplie par millions de paquets par seconde et devient un goulot d'étranglement inacceptable.
iptables, avec son approche simpliste de règles linéaires et triviales, n'a pas ce problème car le fractionnement des chaînes réduit la portion de règles évaluées pour chaque paquet à un sous-ensemble gérable. Est-ce moins élégant ? Absolument. Est-ce obsolète ? Absolument. Est-ce une mauvaise pratique, inesthétique et désagréable ? Encore une fois, oui, oui, oui ! Est-ce que ça fonctionne ? Absolument.
Le reste, ce ne sont que des paroles en l'air.
Les Lezioni apprécient
1. Les benchmarks ne constituent pas votre cas d'utilisation
Le test de performance Red Hat 2017 évaluait la vitesse de recherche sur des ensembles de plusieurs milliers d'éléments, mais dans des conditions contrôlées. Il ne simulait pas le comportement d'un serveur réel, avec un résolveur DNS local, des connexions SSH actives, un serveur web et divers services, devant évaluer chaque paquet par rapport à un ensemble de 51 000 préfixes. Les tests synthétiques mesurent le débit maximal ; les tests en conditions réelles mesurent la latence sous charge mixte.
2. « Plus moderne » ne signifie pas « meilleur dans tous les cas de figure ».
nftables est objectivement supérieur à iptables dans la plupart des cas. Cependant, le génie logiciel implique des compromis, et le choix de la structure de données pour les ensembles d'intervalles (arbre binaire de recherche ou table de hachage) est un compromis qui pénalise fortement les très grands ensembles. Il ne s'agit pas d'un bug, mais d'une conséquence architecturale.
3. Toujours tester avec des données réelles
Si nous avions testé immédiatement avec 51 000 préfixes réels au lieu de quelques centaines de règles de test, nous aurions découvert le problème pendant le développement. La leçon est simple : si votre système doit gérer N éléments en production, testez-le avec N éléments en développement.
4. Le plan de repli est essentiel.
CFM prend désormais en charge trois systèmes de contrôle d'accès (iptables, nftables et firewalld), tous trois utilisant le fractionnement de chaînes pour le géoblocage. Cette architecture multi-systèmes nous a permis d'isoler rapidement le problème et de mettre en œuvre la solution sans avoir à réécrire l'intégralité du système.
conclusion
Nous n'écrivons pas cet article pour dénigrer nftables — nous l'utilisons quotidiennement et apprécions ses qualités. Nous l'écrivons car, d'après notre expérience, l'idée reçue (« nftables est toujours plus rapide qu'iptables ») mérite une précision importante : cela dépend du cas d'utilisation.
Si vous gérez des ensembles de quelques milliers d'adresses IP précises (listes noires, limitation de débit, fail2ban), nftables avec set hash est une excellente solution. En revanche, si vous devez bloquer géographiquement des dizaines de milliers de préfixes CIDR, il est fortement conseillé d'envisager la division de chaînes avec iptables avant de considérer nftables comme le choix approprié.
Parfois, la solution « classique » est plus efficace. Et dans notre secteur — l’ingénierie des systèmes Linux de production — l’efficacité prime toujours sur l’élégance.
Références
- Red Hat : Évaluation comparative des nftables (2017)
- Red Hat : Optimisation des performances des grands ensembles de règles iptables-nft dans l’espace utilisateur (2020)
- Bug Netfilter n° 1735 : les intervalles s’allongent progressivement (2024)
- Bug Netfilter n° 1439 : rechargement des ensembles de données volumineux excessivement lent (2020)
- OpenWrt : Nftables rencontre des difficultés avec les très grands ensembles de données.
- OpenWrt : Quelques réflexions sur les performances de nftables
- Code source du noyau : nft_set_pipapo.c (backend pipapo/rbtree)
- nft-geo-filter — Outil de géoblocage pour les nftables



