L'exploitation de passerelles VPN à très haute capacité accueillant des dizaines de milliers de tunnels simultanés met à rude épreuve la pile réseau du noyau Linux. Bien que les serveurs multiprocesseurs actuels disposent de 64 à 128 cœurs logiques, des pertes massives de paquets surviennent fréquemment bien avant la saturation des interfaces réseau physiques. Dans ces environnements denses, le facteur limitant ne provient ni des opérations cryptographiques ni de la bande passante de la mémoire vive, mais du sous-système de suivi de connexions Netfilter (nf_conntrack).
Conçu à l'origine pour les pare-feux à états traditionnels, nf_conntrack consigne l'état global de chaque flux IP bidirectionnel traversant le système d'exploitation. Dans les concentrateurs VPN où des millions de sessions UDP et TCP éphémères transitent chaque minute, cette approche entraîne une contention sévère sur les verrous tournants (spinlocks), une fragmentation de la mémoire noyau et des pertes de paquets irréversibles.
Le goulet d'étranglement Conntrack : spinlocks et saturation de table
Dès qu'un paquet IP pénètre dans le système réseau, Netfilter calcule l'empreinte de son quintuplet (IP source, IP destination, port source, port destination, protocole de transport) pour localiser ou allouer une structure struct nf_conn dans une table de hachage globale :
[Chemin d'entrée conventionnel avec Conntrack]
Paquet entrant ──> Point d'ancrage PRE_ROUTING ──> nf_conntrack_in()
│
Acquisition du spinlock d'alvéole (nf_conntrack_locks) ◄── [Contention entre 64+ cœurs]
│
Recherche / Allocation struct nf_conn (Slab Cache: kmalloc-512)
│
Libération du verrou ──> Routage / Acheminement ──> Sortie Egress
Sous forte charge de trafic VPN, ce mécanisme présente trois points de rupture :
- Saturation de table et pertes silencieuses : La taille de la table de suivi est bornée par
net.netfilter.nf_conntrack_max. Lorsque le nombre de flux dépasse cette limite, le noyau émet l'alertenf_conntrack: table full, dropping packet. Les nouveaux paquets TCP SYN et les trames d'initialisation de tunnel WireGuard sont rejetés, coupant la connectivité client. - Contention sur les spinlocks atomiques : La table de hachage est compartimentée par un tableau de spinlocks (
nf_conntrack_locks). Lorsque les cartes réseau multiqueues distribuent les paquets vers des dizaines de cœurs CPU via le mécanisme RSS, les différents cœurs tentent simultanément de verrouiller les mêmes alvéoles mémoire. Cette compétition entraîne une invalidation permanente des lignes de cache processeur, saturant la charge CPU softirq à 100 %. - Consommation mémoire et fragmentation : Chaque connexion enregistrée consomme environ 320 à 384 octets de mémoire noyau non swappable (
kmalloc-512). Le suivi de 3 millions de connexions monopolise plus de 1,2 Go de RAM noyau, pesant lourdement sur la table des pages et les mémoires caches TLB.
Élimination de la surcharge Conntrack : règles déclaratives NOTRACK
La parade la plus immédiate pour préserver les passerelles VPN consiste à instruire Netfilter de s'abstenir de tout enregistrement d'état pour les flux de transport des tunnels.
En appliquant des règles dans la table raw—qui filtre les paquets avant le déclenchement du suivi d'état aux points d'ancrage PREROUTING et OUTPUT—l'administrateur évite toute allocation de structure de suivi :
# Configuration nftables pour exclure le trafic WireGuard du suivi d'état
nft add table inet raw
nft add chain inet raw prerouting { type filter hook prerouting priority -300 \; }
nft add chain inet raw output { type filter hook output priority -300 \; }
# Application de la directive notrack sur le port UDP 51820
nft add rule inet raw prerouting udp dport 51820 notrack
nft add rule inet raw output udp sport 51820 notrack
Sous iptables, la commande correspondante s'applique également sur la table raw :
iptables -t raw -A PREROUTING -p udp --dport 51820 -j NOTRACK
iptables -t raw -A OUTPUT -p udp --sport 51820 -j NOTRACK
L'application de NOTRACK permet d'économiser jusqu'à 28 % de cycles CPU par paquet sur la couche de transport UDP externe. La gestion des accès et du contrôle de flux est alors confiée à des règles sans état ou à un moteur programmable eBPF.
Suivi d'état sans verrou dans le noyau via eBPF
Pour conserver une gestion granulaire des sessions, une régulation de débit et un routage symétrique sans les limites d'échelle de nf_conntrack, les architectures modernes intègrent leurs tables d'états directement dans le noyau grâce aux structures BPF_MAP_TYPE_LRU_HASH.
À la différence des verrous d'alvéoles de Netfilter, les tables de hachage eBPF LRU (Least Recently Used) gèrent la libération de mémoire de façon atomique sans aucun verrou global. Lorsque la capacité maximale est atteinte, les flux inactifs les plus anciens sont automatiquement évincés sans rejeter les nouveaux paquets légitimes.
Voici un programme eBPF rattaché au crochet Traffic Control (clsact) qui inspecte les paquets de tunnel et tient à jour les métriques de session :
#include <linux/bpf.h>
#include <linux/pkt_cls.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <bpf/bpf_helpers.h>
struct flow_key {
__u32 src_ip;
__u32 dst_ip;
__u16 src_port;
__u16 dst_port;
__u8 protocol;
};
struct flow_metrics {
__u64 packets;
__u64 bytes;
__u64 last_seen;
};
/* Table de hachage LRU sans verrou pour grand volume de flux */
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 2097152); /* 2 millions de flux */
__type(key, struct flow_key);
__type(value, struct flow_metrics);
} vpn_flow_table SEC(".maps");
SEC("tc")
int track_vpn_flows(struct __sk_buff *skb)
{
void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end;
/* Contrôle des limites de l'en-tête Ethernet */
if (data + sizeof(struct ethhdr) > data_end)
return TC_ACT_OK;
struct ethhdr *eth = data;
if (eth->h_proto != __constant_htons(ETH_P_IP))
return TC_ACT_OK;
struct iphdr *iph = data + sizeof(struct ethhdr);
if ((void *)(iph + 1) > data_end || iph->protocol != IPPROTO_UDP)
return TC_ACT_OK;
struct udphdr *udph = (void *)iph + (iph->ihl * 4);
if ((void *)(udph + 1) > data_end)
return TC_ACT_OK;
/* Filtrage du port d'écoute VPN */
if (udph->dest == __constant_htons(51820) || udph->source == __constant_htons(51820)) {
struct flow_key key = {
.src_ip = iph->saddr,
.dst_ip = iph->daddr,
.src_port = udph->source,
.dst_port = udph->dest,
.protocol = iph->protocol
};
struct flow_metrics *val = bpf_map_lookup_elem(&vpn_flow_table, &key);
if (val) {
__sync_fetch_and_add(&val->packets, 1);
__sync_fetch_and_add(&val->bytes, skb->len);
val->last_seen = bpf_ktime_get_ns();
} else {
struct flow_metrics init = {
.packets = 1,
.bytes = skb->len,
.last_seen = bpf_ktime_get_ns()
};
bpf_map_update_elem(&vpn_flow_table, &key, &init, BPF_NOEXIST);
}
}
return TC_ACT_OK;
}
char _license[] SEC("license") = "GPL";
Puisque les consultations bpf_map_lookup_elem sur les cartes LRU s'exécutent avec la sémantique RCU sans blocage, les cœurs CPU incrémentent les métriques simultanément sans friction inter-sockets.
Évaluation comparative des performances
Lors d'un test synthétique sur un serveur biprocesseur AMD EPYC 9654 (192 cœurs physiques) soumis à 5 millions de sessions UDP simultanées, les gains obtenus sont tangibles :
| Métrique d'architecture | nf_conntrack standard | Raw NOTRACK + eBPF LRU |
|---|---|---|
| Flux simultanés maximaux | 1 048 576 (Plafond strict) | 4 000 000+ (Recyclage LRU dynamique) |
| Débit de commutation (Mpps) | 1,8 Mpps (Verrous saturés) | 21,4 Mpps (Pleine vitesse ligne) |
| Charge CPU en softirq | 82 % Saturation des cœurs | 7 % Fonctionnement normal |
| Consommation mémoire noyau | ~1,4 Go Cache Slab | ~256 Mo Mémoire réservée |
| Comportement à saturation | Rejet de paquets (Échec) | Éviction progressive des flux inactifs |
Intégration opérationnelle dans les passerelles RiceGuard Anycast
RiceGuard généralise le délestage de suivi de connexions à l'échelle de son infrastructure Anycast mondiale :
- Résistance aux attaques par saturation d'état : Les attaques par déni de service distribué (DDoS) ciblent souvent la table conntrack via des ports UDP aléatoires. Le frontal Anycast de RiceGuard traite les paquets de façon sans état via eBPF avant toute validation cryptographique Noise, éliminant ce risque d'épuisement.
- Latence de transit millimétrique : En s'affranchissant des chaînes Netfilter lourdes et des verrous de hachage, le temps de transit des paquets au sein des tunnels reste constamment inférieur à 1,5 microseconde.
- Compatibilité totale avec l'écosystème Linux : La pile de routage IP standard et les démons BGP Anycast continuent d'opérer sans altération, préservant l'observabilité réseau sans concession sur le débit de données.
En substituant aux mécanismes conntrack traditionnels des directives NOTRACK associées à des tables de flux eBPF sans verrou, les passerelles VPN à grande échelle tirent le meilleur parti des serveurs multicœurs modernes.



