Comment bloquer plusieurs millions de paquets par seconde avec XDP ?
Bloquer millions paquets XDP est devenu essentiel pour tout administrateur système confronté à des attaques DDoS modernes capables de générer plusieurs dizaines de millions de paquets par seconde. XDP (eXpress Data Path) offre un hook kernel ultra-rapide situé directement après la réception des paquets par la carte réseau, avant même l'allocation de sk_buff, permettant de filtrer des flux massifs avec une latence sub-microseconde par paquet. Ce tutoriel 2026 détaille comment implémenter une protection XDP performante en production, couvrant architecture, règles eBPF, intégration nftables et métriques temps réel.
Pourquoi XDP pour bloquer des millions de paquets par seconde ?
Les firewalls traditionnels basés sur iptables ou nftables seuls peinent face aux attaques volumétriques : chaque paquet traverse la pile réseau complète (netfilter hooks, allocation mémoire sk_buff, traitement TCP/IP) avant filtrage, consommant CPU et RAM de manière linéaire. À partir de quelques millions de pps, le serveur sature. XDP intervient au niveau du driver réseau (RX queue), exécute un programme eBPF qui décide en quelques nanosecondes (XDP_DROP, XDP_PASS, XDP_TX, XDP_REDIRECT) du sort du paquet, et peut le rejeter sans allocation mémoire coûteuse.
Les benchmarks terrain montrent qu'un CPU moderne (ex. AMD EPYC 7003, Intel Xeon Scalable 3rd Gen) équipé d'un NIC compatible XDP native (Intel X710, Mellanox ConnectX-5) peut filtrer entre 20 et 80 millions de pps par cœur selon la complexité du programme eBPF. En mode XDP_DROP pur, le surcoût CPU reste inférieur à 1 % jusqu'à plusieurs dizaines de Mpps, là où iptables atteindrait 100 % de charge dès 2-3 Mpps.
Architecture XDP : hook RX et verdict immédiat
Le programme XDP est attaché à une interface réseau via ip link set dev eth0 xdp obj programme.bpf.o sec xdp. Chaque paquet entrant déclenche l'exécution du programme eBPF qui :
- Parse les en-têtes Ethernet, IP, TCP/UDP/ICMP en accédant directement au buffer DMA (
xdp_md->data,xdp_md->data_end). - Consulte des BPF maps (tableaux associatifs kernel-space partagés) contenant règles, compteurs, blacklists IP.
- Retourne un verdict : XDP_DROP (rejet silencieux, zéro allocation), XDP_PASS (paquet continue vers netfilter/nftables), XDP_TX (renvoie sur la même interface, utile pour reflection defense), XDP_REDIRECT (envoie vers une autre interface ou un AF_XDP socket user-space).
Cette architecture stateless garantit une scalabilité horizontale : chaque cœur CPU traite sa RX queue indépendamment (RSS, Receive Side Scaling). Contrairement à netfilter, XDP ne maintient pas de table de connexion (conntrack) par défaut, ce qui évite le verrouillage de structures partagées et permet un parallélisme parfait.
PAKKT.io exploite cette architecture en déployant un programme XDP unique par interface (le PAKKT Engine), capable de gérer jusqu'à 256 règles simultanées pilotées par BPF maps. Chaque règle définit port range, protocole (TCP/UDP/ICMP/any), action (block / rate_limit / allow_only), max_pps global et par port, taille paquet min/max. L'agent Go léger synchronise règles et métriques avec le panel centralisé en mTLS, avec un impact système inférieur à 1 % CPU et moins de 5 MB RAM.

Implémenter des règles XDP pour bloquer millions paquets : code eBPF et BPF maps
Un programme XDP typique pour filtrer par port et taux se structure autour de BPF maps partagées avec l'espace utilisateur. Exemple simplifié (syntaxe C/eBPF) :
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <bpf/bpf_helpers.h>
struct rule {
__u16 port_start;
__u16 port_end;
__u8 protocol; // IPPROTO_TCP, IPPROTO_UDP, etc.
__u8 action; // 0=DROP, 1=PASS, 2=RATELIMIT
__u32 max_pps;
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, __u32);
__type(value, struct rule);
__uint(max_entries, 256);
} rules_map SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__type(key, __u32);
__type(value, __u64);
__uint(max_entries, 256);
} counters_map SEC(".maps");
SEC("xdp")
int xdp_firewall(struct xdp_md *ctx) {
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_PASS;
if (eth->h_proto != __constant_htons(ETH_P_IP))
return XDP_PASS;
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end)
return XDP_PASS;
__u16 dport = 0;
if (ip->protocol == IPPROTO_TCP) {
struct tcphdr *tcp = (void *)ip + (ip->ihl * 4);
if ((void *)(tcp + 1) > data_end)
return XDP_PASS;
dport = __bpf_ntohs(tcp->dest);
} else if (ip->protocol == IPPROTO_UDP) {
struct udphdr *udp = (void *)ip + (ip->ihl * 4);
if ((void *)(udp + 1) > data_end)
return XDP_PASS;
dport = __bpf_ntohs(udp->dest);
} else {
return XDP_PASS;
}
// Parcourir les règles
#pragma unroll
for (__u32 i = 0; i < 256; i++) {
struct rule *r = bpf_map_lookup_elem(&rules_map, &i);
if (!r || r->action == 0xFF) // règle inactive
continue;
if (r->protocol != 0 && r->protocol != ip->protocol)
continue;
if (dport < r->port_start || dport > r->port_end)
continue;
// Incrémenter compteur
__u64 *count = bpf_map_lookup_elem(&counters_map, &i);
if (count)
__sync_fetch_and_add(count, 1);
if (r->action == 0) // DROP
return XDP_DROP;
if (r->action == 2) { // RATELIMIT (simplifié, véritable implémentation nécessite token bucket ou sliding window)
// Ici on DROP si count dépasse seuil (approximatif)
if (count && *count > r->max_pps)
return XDP_DROP;
}
return XDP_PASS;
}
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
Ce code illustre la logique : parsing des en-têtes, consultation d'une BPF map de type BPF_MAP_TYPE_ARRAY pour les règles, compteurs per-CPU pour éviter la contention (BPF_MAP_TYPE_PERCPU_ARRAY), verdict immédiat. En production, PAKKT Engine intègre une gestion avancée : support ICMP, packet size min/max, rate-limit par port (max_port_pps), et synchronisation automatique des règles depuis le panel via l'agent.
Compilation et chargement du programme eBPF
Pour compiler ce programme, utilisez clang avec le backend BPF :
clang -O2 -target bpf -c xdp_firewall.c -o xdp_firewall.bpf.o
Puis attachez-le à l'interface réseau (remplacez eth0 par votre interface publique) :
ip link set dev eth0 xdp obj xdp_firewall.bpf.o sec xdp
Vérifiez l'attachement :
ip link show dev eth0
Vous devriez voir xdp dans la sortie. Pour inspecter les BPF maps chargées :
bpftool map list
bpftool map dump name rules_map
bpftool map dump name counters_map
La modification des règles en temps réel se fait via bpftool map update, mais en production un agent automatise cette gestion. PAKKT expose une API REST pour ajouter/supprimer règles, et l'agent local synchronise les BPF maps toutes les 30 secondes (heartbeat mTLS).
Rate-limiting stateless dans XDP : limites et solutions
XDP est par nature stateless : chaque paquet est traité indépendamment, sans mémoire inter-paquets. Implémenter un rate-limit précis (ex. 1000 pps sur un port pendant une fenêtre glissante de 1 seconde) nécessite des structures BPF complexes (token bucket, compteurs par seconde avec timestamp). Les limitations du verifier eBPF (boucles limitées, stack restreint) imposent des compromis.
En pratique, le rate-limit XDP dans PAKKT Engine utilise un compteur per-règle réinitialisé périodiquement par l'agent (toutes les secondes), offrant une approximation du max_pps. Pour un rate-limit strict par connexion (ex. limiter SYN par IP source), déléguer à nftables est plus adapté : XDP DROP les paquets massivement, nftables (avec conntrack et meter) affine le filtrage stateful sur le trafic restant.

Compléter XDP avec nftables stateful pour un filtrage multi-couches
XDP excelle pour rejeter des millions de paquets malveillants en amont, mais ne gère pas le suivi de connexion (conntrack). Pour autoriser uniquement les connexions TCP établies, limiter les SYN par IP source, ou bloquer des flags TCP invalides, nftables en aval de XDP apporte la couche stateful nécessaire.
Architecture recommandée : XDP DROP en première ligne (blacklists IP, ports fermés, rate-limit global), puis paquet passe (XDP_PASS) vers netfilter où nftables applique des règles contextuelles. PAKKT déploie une table isolée inet pakkt qui coexiste sans conflit avec Docker (table nat), fail2ban (chaînes iptables), ou iptables-persistent.
Exemple de règle nftables complémentaire
Créer la table et la chaîne input :
nft add table inet pakkt
nft add chain inet pakkt input { type filter hook input priority filter\; policy accept\; }
Autoriser uniquement les connexions établies et relatives, limiter les nouvelles connexions TCP sur le port 25565 (Minecraft) à 50/seconde :
nft add rule inet pakkt input ct state established,related accept
nft add rule inet pakkt input tcp dport 25565 ct state new limit rate 50/second accept
nft add rule inet pakkt input tcp dport 25565 drop
Bloquer les paquets TCP avec flags invalides (SYN+FIN, etc.) :
nft add rule inet pakkt input tcp flags \& \(fin\|syn\) == fin\|syn drop
nft add rule inet pakkt input tcp flags \& \(syn\|rst\) == syn\|rst drop
Rate-limit ICMP (éviter flood ping) :
nft add rule inet pakkt input icmp type echo-request limit rate 10/second accept
nft add rule inet pakkt input icmp type echo-request drop
Ces règles nftables s'exécutent uniquement sur le trafic ayant passé XDP. Si XDP a déjà rejeté 95 % des paquets d'une attaque, nftables traite seulement les 5 % restants (trafic légitime ou attaques sophistiquées nécessitant conntrack). Le surcoût CPU reste ainsi maîtrisé. PAKKT génère automatiquement ces règles nftables en fonction de la configuration du panel (port range, protocol, rule_type allow_only) et les synchronise avec l'agent.
Synchronisation blacklist/whitelist IP double couche
PAKKT maintient deux listes par agent : blacklist (IPs à bloquer) et whitelist (IPs toujours autorisées). Chaque liste est déployée en double couche :
- Couche XDP : BPF map de type
BPF_MAP_TYPE_HASHouBPF_MAP_TYPE_LPM_TRIE(pour CIDR), consultée avant toute règle. Blacklist → XDP_DROP immédiat, whitelist → XDP_PASS court-circuitant les autres règles. - Couche nftables : set nft (type
ipv4_addr), règlenft add rule inet pakkt input ip saddr @blacklist dropen début de chaîne,ip saddr @whitelist acceptprioritaire.
L'agent PAKKT synchronise ces listes toutes les 30 secondes via heartbeat mTLS. Lors d'un ajout manuel d'IP au panel (ex. après détection d'une attaque), la mise à jour est propagée en moins d'une minute sur tous les agents concernés. Cette redondance garantit qu'un paquet blacklisté sera rejeté soit en XDP (économie CPU maximale), soit en nftables si XDP n'est pas supporté ou désactivé.
Métriques temps réel et monitoring de la protection XDP
Bloquer des millions de paquets sans visibilité sur ce qui est filtré rend le débogage impossible. Un système de production doit exposer des métriques par règle, par port, par IP source, avec historique et alertes. PAKKT centralise ces données dans un panel accessible via dashboard web temps réel.
Collecte des compteurs XDP via BPF maps
Le programme XDP incrémente des compteurs per-CPU dans une BPF map BPF_MAP_TYPE_PERCPU_ARRAY. L'agent PAKKT lit ces compteurs périodiquement (toutes les secondes) avec bpf_map_lookup_elem, agrège les valeurs de tous les cœurs, et envoie les métriques au backend via mTLS. Exemple de lecture (pseudo-code Go) :
import "github.com/cilium/ebpf"
countersMap, _ := ebpf.LoadPinnedMap("/sys/fs/bpf/pakkt_counters")
var values []uint64
countersMap.Lookup(ruleID, &values)
totalPackets := uint64(0)
for _, v := range values {
totalPackets += v
}
// Envoyer totalPackets au backend PAKKT
Le backend PAKKT insère ces métriques dans TimescaleDB (extension PostgreSQL optimisée pour séries temporelles), permettant agrégations SQL rapides (pps moyen par minute, top ports attaqués, évolution sur 7 jours). Le panel affiche graphiques interactifs (Recharts.js), carte GeoIP mondiale (localisation des IPs sources d'attaque), top IPs et top pays.
Logs internes agent et audit log centralisé
L'agent Go enregistre localement (/var/log/pakkt-agent.log) les événements : règles mises à jour, erreurs de chargement XDP, heartbeat failures. Ces logs sont également streamés au panel (chiffrés mTLS) pour consolidation. Le panel expose un audit log complet : qui a modifié quelle règle, quand, depuis quelle IP, avec quelle clé API.
Exemple de log agent lors d'un reload de règle :
[INFO] 2026-01-15T10:23:45Z Heartbeat received: 3 rules updated
[INFO] 2026-01-15T10:23:46Z XDP map rules_map updated: rule_id=12 port_range=25565-25565 action=block
[INFO] 2026-01-15T10:23:46Z XDP counters synced: rule_id=12 dropped_packets=2847392 (last 30s)
Ces logs permettent de corréler une modification de règle avec un changement de trafic observé (ex. après ajout d'une règle block sur le port 80, les compteurs confirment-ils le DROP ?).
Intégration avec panels tiers : Pterodactyl, Pelican, WHMCS
PAKKT s'intègre aux panels de gestion de serveurs de jeu existants via API publique. L'intégration Pterodactyl v1.x est fonctionnelle : un plugin Pterodactyl affiche les métriques PAKKT directement dans l'interface serveur (pps entrant, règles actives, top IPs bloquées). Les utilisateurs Pterodactyl peuvent activer/désactiver des règles prédéfinies (ex. protection anti-bot Minecraft) sans quitter leur panel.
Les intégrations Pelican et WHMCS sont prévues courant 2026. L'API PAKKT (https://api.pakkt.io/v1/agents/{agent_id}/rules, authentification via clé API) permet à tout panel tiers de créer/modifier des règles, récupérer les métriques JSON temps réel, et déclencher des actions (ex. blacklist temporaire d'une IP). Un marketplace de templates XDP/nft partagés par la communauté facilite le déploiement rapide de configurations type (protection FiveM, protection Rust, etc.).
Alertes et automatisation
Le panel PAKKT permet de configurer des alertes webhook (Discord, Slack, email) déclenchées par seuils : pps entrant > 10 Mpps pendant 60s, règle spécifique droppant > 1M paquets en 5 minutes, agent offline > 2 minutes. Ces alertes permettent une réaction rapide (ex. ajouter manuellement une IP en blacklist, escalader vers un scrubbing cloud si l'attaque dépasse la capacité du serveur).
Automatisation avancée : l'API PAKKT peut être appelée par des scripts externes (ex. fail2ban déclenche un ajout en blacklist XDP après N tentatives SSH échouées, un honeypot interne remonte les IPs scannant des ports fermés). Cette intégration multi-outils centralise la défense réseau autour de PAKKT comme pivot kernel-level.

Conclusion
Bloquer des millions de paquets XDP en production nécessite un programme eBPF optimisé, des BPF maps partagées pour configuration dynamique, une intégration nftables stateful pour le filtrage fin, et un système de métriques centralisées garantissant visibilité et auditabilité. XDP offre une scalabilité horizontale inégalée (plusieurs dizaines de Mpps par cœur), mais exige rigueur dans le parsing des en-têtes et respect des contraintes du verifier eBPF. Associé à nftables pour la couche stateful et à un agent léger de synchronisation, ce stack constitue une défense kernel robuste contre les attaques volumétriques modernes, tout en maintenant une latence applicative négligeable et un surcoût CPU inférieur à 1 %.
FAQ
Peut-on charger plusieurs programmes XDP sur la même interface pour gérer plus de 256 règles ?
Non, une interface réseau ne peut avoir qu'un seul programme XDP attaché à la fois (limitation kernel). Pour dépasser 256 règles, il faut soit augmenter la taille de la BPF map dans le code source (recompiler le programme eBPF avec max_entries supérieur), soit chaîner les règles en plusieurs passes (tail calls eBPF via BPF_MAP_TYPE_PROG_ARRAY), technique complexe avec overhead additionnel. PAKKT Engine limite à 256 règles par agent pour garantir performance et simplicité, suffisant pour la majorité des cas d'usage. Si besoin réel de plus de règles, déployer plusieurs agents PAKKT sur plusieurs interfaces ou VLAN distincts.
Comment PAKKT gère-t-il les mises à jour de règles sans interruption de service (zero downtime) ?
L'agent PAKKT met à jour les BPF maps (rules_map, blacklist_map) via bpf_map_update_elem en espace utilisateur, sans recharger le programme XDP. Ces opérations atomiques (kernel garantit thread-safety) prennent effet immédiatement, paquet suivant applique déjà la nouvelle règle. Pas de détachement/réattachement du programme XDP, donc zéro perte de paquets pendant la mise à jour. Le heartbeat toutes les 30s synchronise incrémentalement (uniquement les règles modifiées), minimisant la latence de propagation.
XDP peut-il bloquer des attaques DDoS applicatives (ex. HTTP flood, slowloris) ?
Non, XDP opère au niveau L2/L3/L4 (Ethernet, IP, ports TCP/UDP), il ne parse pas les protocoles applicatifs (HTTP, DNS, etc.). Une attaque HTTP flood avec des requêtes GET légitimes passera XDP si les paquets TCP sont valides. Pour filtrer L7, il faut soit un reverse proxy applicatif (nginx, HAProxy avec rate-limit HTTP), soit un module netfilter spécialisé (ex. xt_string pour patterns HTTP), soit un scrubbing cloud avec WAF. PAKKT protège contre les floods volumétriques (SYN flood, UDP amp, ICMP flood) et rate-limite les nouvelles connexions (nftables ct state new), mais ne remplace pas un WAF pour les attaques L7 sophistiquées.
Déployez PAKKT en 30 secondes
Protection kernel double couche XDP + nftables, pilotable depuis un panel centralisé. Essai gratuit 7 jours.