Development
10 min read
35 views

Scaling QUIC Ingress : Steering de socket eBPF pour la migration de connexion HTTP/3

IT
InstaTunnel Team
Published by our engineering team
Scaling QUIC Ingress : Steering de socket eBPF pour la migration de connexion HTTP/3

Quick answer

Scaling HTTP/3 pour la télémétrie à haute fréquence : steering de socket eBPF: MCP tunnel answer

MCP tunneling gives a local MCP server a public HTTPS endpoint so AI tools can reach it during development without deploying the server first.

What is MCP tunneling?

MCP tunneling exposes a local Model Context Protocol server through a public endpoint so compatible AI tools can connect during development.

When should I use InstaTunnel for MCP?

Use InstaTunnel Pro when a local MCP endpoint needs public HTTPS access, stable routing, and stream-friendly tunnel behavior.

Lorsqu’un nœud edge distant se déconnecte du réseau pendant quelques centaines de millisecondes et revient avec une nouvelle adresse IP, une déploiement naïf de proxy UDP tuera silencieusement la session qui devait survivre à ce type de perturbation. Cet article examine pourquoi cela se produit, et comment le steering de socket basé sur eBPF au niveau du noyau le corrige — en utilisant les mécanismes réels fournis par Linux et Cloudflare, pas seulement la théorie.

Pourquoi QUIC, et pourquoi cela casse le load balancing naïf

La télémétrie en temps réel — réseaux de capteurs industriels, fusion de capteurs pour véhicules autonomes, charges de travail en edge mobile — a largement migré de TCP vers le transport QUIC d’HTTP/3. La livraison stricte dans l’ordre de TCP signifie qu’un seul paquet perdu bloque chaque flux multiplexé sur cette connexion (blocage head-of-line). QUIC évite cela en gérant sa propre récupération de perte et multiplexage de flux directement sur UDP, donc un paquet perdu sur un flux ne bloque pas les autres.

QUIC supporte aussi le 0-RTT — mais il est important d’être précis : le 0-RTT permet à un client de retour de reprendre une session précédente et d’envoyer immédiatement des données applicatives, en utilisant une clé pré-partagée d’un handshake antérieur. Un client totalement nouveau doit encore effectuer un handshake TLS 1.3 complet en 1-RTT ; le 0-RTT est une optimisation de reprise, pas une propriété de chaque handshake QUIC.

La fonctionnalité la plus importante pour cet article est la migration de connexion. Une connexion TCP est liée à un 4-tuple — IP source, port source, IP destination, port destination. Modifier l’un de ces éléments (par exemple, un téléphone passant du Wi-Fi à la 5G, ou un robot se déplaçant entre points d’accès) et la connexion est perdue ; le client doit renégocier à partir de zéro. QUIC découple la session du chemin réseau en l’identifiant avec un Connection ID (CID) plutôt qu’avec le 4-tuple. Selon la RFC 9000, un CID peut faire jusqu’à 20 octets et est opaque pour le pair — le serveur le choisit, le transmet au client, et peut continuer à reconnaître ce client même après que son IP et son port changent en cours de session.

C’est une énorme avancée pour un seul client parlant à un seul serveur. Cela devient problématique lorsque le côté serveur est en réalité une flotte de processus de travail équilibrés.

Le hash du 4-tuple échoue lors de migration

Les reverse proxies comme NGINX, Envoy, et HAProxy évoluent sur plusieurs cœurs CPU en exécutant plusieurs processus de travail, chacun avec sa propre socket liée au même port via SO_REUSEPORT. Pour TCP, c’est simple : le noyau gère le handshake et accept() attribue une connexion complétée à un seul worker, qui continue à router vers celui-ci pendant toute la durée de la connexion.

UDP n’a pas de handshake ni d’état de connexion persistant côté noyau, donc SO_REUSEPORT retombe sur un mécanisme beaucoup plus simple : pour chaque datagramme entrant, le noyau calcule un hash du 4-tuple et choisit une socket dans le groupe reuseport selon ce hash. Tant que le 4-tuple reste fixe, chaque paquet atterrit sur le même worker.

Dès que l’IP du client change — ce qui est précisément le but de la migration de connexion QUIC —, le 4-tuple change, le hash change, et le noyau route le paquet vers un différent worker qui n’a jamais vu ce client, ne détient pas ses clés TLS, et doit donc abandonner le paquet. La fonctionnalité phare de QUIC est neutralisée par un mécanisme de load balancing qui le précède.

Apprendre au noyau à gérer QUIC avec eBPF

Plutôt que d’intégrer une connaissance spécifique de QUIC dans le noyau, Linux permet d’attacher un programme eBPF personnalisé à un groupe reuseport et de laisser celui-ci décider de la sélection de socket au lieu du hash par défaut. Cette capacité est BPF_PROG_TYPE_SK_REUSEPORT, ajoutée par Martin KaFai Lau dans Linux 4.19, et elle fonctionne avec l’aide bpf_sk_select_reuseport(), qui assigne un paquet entrant à une socket spécifique dans une carte BPF_MAP_TYPE_REUSEPORT_SOCKARRAY (et, depuis Linux 5.8, aussi avec SOCKHASH/SOCKMAP). Si le programme eBPF retourne un index invalide, le noyau retombe silencieusement sur le hash par défaut du 4-tuple, ce qui garantit une dégradation sûre.

Cela permet de remplacer « hacher le 4-tuple » par « lire le CID QUIC dans le paquet et router selon cela » — entièrement dans le noyau, avant que le paquet n’atteigne un buffer socket utilisateur.

La pipeline de steering

  1. Le worker intègre son identité dans le CID. Lors du tout premier paquet de handshake, avant toute migration, le hash par défaut est sans danger — il n’y a pas encore d’état établi pour mal-router. Le worker qui gère le handshake (par exemple, Worker 2) génère le Server Connection ID qu’il renvoie au client, et encode son propre index quelque part dans ces octets avec une entropie cryptographique.
  2. Le programme eBPF parse l’en-tête QUIC dans le noyau. Sur chaque paquet suivant, le programme sk_reuseport inspecte la charge utile brute via struct sk_reuseport_md, distingue l’en-tête long (paquets de handshake) de l’en-tête court (paquets 1-RTT en état stable), et extrait le champ Destination Connection ID.
  3. Recherche de l’ID du worker, pas un scan de table de hachage. Parce que l’ID du worker est directement intégré dans le CID plutôt que dans une table de mappage de millions de CIDs vers sockets, le programme eBPF masque simplement les bits pertinents pour récupérer l’entier.
  4. bpf_sk_select_reuseport() effectue le routage. L’ID du worker extrait est utilisé comme index dans le tableau de sockets, et le noyau délivre le datagramme directement à cette socket — peu importe l’IP actuel du client.

Une correction importante : cette idée d’« encoder directement l’info de routage dans le CID » n’est pas une astuce sur-mesure — c’est précisément le problème que la spécification draft-ietf-quic-load-balancers (“QUIC-LB”) de l’IETF a tenté de standardiser, avec une disposition octet définie (un premier octet réservé pour la rotation de config/longueur auto-encodée, suivi par l’ID du serveur/worker à partir du second octet, puis un nonce chiffré ou obfusqué). Il est important d’être précis : QUIC-LB n’a jamais dépassé le stade de Internet-Draft et est maintenant listé comme expiré/inactif dans le datatracker de l’IETF. Ce n’est pas une norme adoptée. Cela ne rend pas la technique fictive — de nombreux load balancers et proxies implémentent leur propre variante — mais ce n’est pas une norme officielle, juste une convention bien documentée et non officielle.

eBPF n’est pas un environnement de scripting général

Il est utile d’être concret sur pourquoi le programme eBPF doit être aussi limité et léger, plutôt que de supposer des « restrictions » vagues. Le vérificateur en-kernel prouve statiquement qu’un programme se terminera et restera mémoire-safe avant son chargement :

  • Chaque programme est limité à 512 octets de pile.
  • Les boucles non bornées ont été rejetées jusqu’à Linux 5.3, qui a introduit des « boucles bornées » prouvablement terminantes ; avant cela, les boucles devaient être dépliées à la compilation.
  • Le vérificateur impose un budget global de complexité (environ un million d’états d’instruction simulés par programme), et le dépasse rapidement si vous utilisez des boucles ou branches excessives.

Rien de tout cela n’est exotique pour une tâche d’analyse d’en-tête comme l’extraction du CID, mais cela explique pourquoi la méthode d’encodage du CID doit rester simple (quelques octets, masqués directement) plutôt que nécessiter une structure de données complexe.

Gestion des redémarrages : ce qui est réellement déployé en production

La problématique initiale, présentée comme « générations de sockets, similaire à l’approche de Cloudflare », sous-estimait la concrétisation en production. Cloudflare a déployé exactement cela dans un projet open-source appelé udpgrm (UDP Graceful Restart Marshal), décrit dans un article de blog d’ingénierie de mai 2025, et il est utile d’en parcourir les détails car il résout le problème de mise à niveau de façon plus rigoureuse qu’un compteur de générations fait maison.

Le problème principal : lors d’un redémarrage ou rechargement d’un proxy QUIC, vous avez deux ensembles de sockets SO_REUSEPORT dans le même groupe — un de l’ancien binaire, drainant ses connexions existantes, et un du nouveau binaire, acceptant de nouvelles connexions. Un routeur eBPF naïf basé sur le CID extraira simplement « Worker 2 » et confiera aveuglément le paquet à nouveau Worker 2, ce qui casse toutes les connexions en cours appartenant à l’ancien Worker 2.

Le modèle de udpgrm :

  • Une génération de socket est l’ensemble des sockets du groupe reuseport appartenant à une instance logique du serveur (c’est-à-dire, un déploiement).
  • Un pointeur de génération active indique à l’eBPF quelle génération doit recevoir les flux neufs.
  • Un dissecteur de flux décide, par paquet, s’il appartient à un flux nouveau (pour QUIC, un paquet initial) ou établi, et si établi, quelle génération de socket le possède — même si c’est une génération plus ancienne en cours de drain.
  • L’état du flux et les références de socket vivent dans une carte SOCKHASH que le daemon maintient et synchronise depuis l’espace utilisateur, séparant cette gestion de l’application.

udpgrm propose trois modes de dissecteur intégrés plus un modèle « sur-mesure » : un FLOW qui suit une table de hachage 4-tuple fixe (utile pour protocoles sans identifiant de connexion natif), un dissecteur CBPF basé sur un cookie où l’identifiant de routage est directement dans le paquet — exactement le schéma CID-QUIC décrit ci-dessus, que Cloudflare appelle un « cookie udpgrm » — et un mode NOOP pour protocoles sans état comme DNS. Le daemon s’intègre à systemd via un petit protocole de contrôle basé sur setsockopt/getsockopt et une astuce de processus « leurre » pour contourner l’hypothèse que systemd ne gère qu’une seule instance.

L’enseignement pratique : ne pas réinventer la gestion de génération et la dissection de flux de zéro, sauf si vous avez une raison très spécifique — udpgrm (ou un daemon eBPF de reuseport testé en production) résout déjà la partie redémarrage gracieux, qui est la plus difficile à faire correctement.

Où en est l’ingress HTTP/3 en entreprise

Le passage de TCP à QUIC résout un problème de transport réel et ancien — mais il met en lumière une hypothèse profondément ancrée dans la gestion Linux du UDP : qu’un « flux » est défini par son 4-tuple. QUIC rejette explicitement cette hypothèse, et le comportement par défaut de SO_REUSEPORT n’a pas encore évolué. BPF_PROG_TYPE_SK_REUSEPORT et bpf_sk_select_reuseport() sont les mécanismes actuels pour combler cette lacune ; QUIC-LB est la tentative de standardisation (maintenant expirée) pour la convention CID ; et udpgrm est un exemple concret, open-source, de ce à quoi ressemble une version de production — routage sensible à la migration et redémarrages sans interruption — aujourd’hui.

Sources


Changelog

Métadonnées supprimées : - Suppression du titre/accroche SEO et du paragraphe final non vérifié qui ressemblait à des métadonnées CMS plutôt qu’à du contenu source.

Corrections : - Clarification que le 0-RTT de QUIC s’applique à la reprise de session avec une clé pré-partagée, pas à chaque handshake — une nouvelle connexion nécessite encore un handshake TLS 1.3 complet en 1-RTT. - Correction de l’exemple d’encodage du worker ID dans le CID : la version initiale disait que l’ID du worker se trouvait « dans les deux premiers octets » du CID. La convention réelle (IETF QUIC-LB) réserve le premier octet pour la rotation/configuration/longueur, et commence l’ID du serveur/worker à partir du second octet. - Ajout du statut précis de standardisation : draft-ietf-quic-load-balancers n’a jamais été transformé en RFC et est listé comme expiré dans le datatracker de l’IETF. Ce n’est pas une norme adoptée. La technique est connue et utilisée dans certains load balancers, mais ce n’est pas une norme officielle. - Remplacement de la mention vague « similaire à l’approche de Cloudflare udpgrm » par une description détaillée vérifiée du fonctionnement de udpgrm (génération de socket, dissecteurs, état SOCKHASH, intégration systemd), directement issue du blog de Cloudflare et du README public. - Confirmation et maintien : BPF_PROG_TYPE_SK_REUSEPORT, bpf_sk_select_reuseport(), BPF_MAP_TYPE_REUSEPORT_SOCKARRAY, limite de 20 octets pour le CID QUIC, mécanisme de hash du 4-tuple qui échoue lors de migration — tout cela vérifié selon RFC 9000, documentation eBPF, et commit kernel original.

Extensions : - Ajout de détails issus de la documentation de l’eBPF sur les contraintes du vérificateur (512 octets de pile, rejet des boucles non bornées avant Linux 5.3, budget de complexité) pour expliquer pourquoi le programme doit rester minimal. - Ajout d’une section complète sur les modes de dissecteur de udpgrm (FLOW, CBPF, NOOP, BESPOKE) et son intégration systemd, car c’est la mise en œuvre concrète du concept de « générations de sockets » évoqué dans la draft initiale. - Ajout d’une section Sources avec liens directs vers toutes les sources principales (RFC, draft IETF, blog Cloudflare, docs eBPF, commit kernel).

Continue from this article into the most relevant product guides and workflows.

Related Topics

#eBPF QUIC load balancing, HTTP/3 sensor ingress proxy, UDP socket steering eBPF, QUIC connection ID parsing, SO_REUSEPORT HTTP3, high-frequency telemetry ingress, sessionless UDP routing, Linux kernel socket filtering, REUSEPORT socket migration, BPF_PROG_TYPE_SK_REUSEPORT, edge device IP migration, connection migration QUIC, real-time sensor data synchronization, industrial IoT gateway 2026, user space worker steering, socket layer packet parsing, uninterrupted telemetry stream, QUIC header inspection, next-gen reverse proxy architecture, kernel-level packet routing, software-defined telemetry ingress, eBPF network data plane, UDP packet hashing, zero-packet-loss failover, hardware-to-cloud low latency, advanced Linux networking, containerized ingress worker pools, QUIC protocol stream stability, sk_buff packet manipulation, telemetry ingress scaling

Keep building with InstaTunnel

Read the docs for implementation details or compare plans before you ship.

Share this article

More InstaTunnel Insights

Discover more tutorials, tips, and updates to help you build better with localhost tunneling.

Browse All Articles