QUIC für den Linux-Kernel
(lwn.net)- Der erste Patch zur offiziellen Integration des QUIC-Protokolls in den Linux-Kernel wurde eingereicht
- Ziel ist es, die Grenzen des bestehenden TCP zu verbessern, etwa Latenz, Head-of-Line-Blocking und durch Zwischenkomponenten verursachte Protokoll-Erstarrung
- QUIC basiert auf UDP, unterstützt Multistreaming und Ende-zu-Ende-Verschlüsselung; bei einer Einführung im Kernel steigt das Potenzial zur Nutzung auf breiteren Plattformen und mit mehr Hardware
- Die Leistung der ersten Kernel-Implementierung wurde zwar niedriger als bei bestehendem TCP und Kernel-TLS gemessen, doch durch künftiges Hardware-Offloading und Optimierungen sind Verbesserungen zu erwarten
- Derzeit wird die Unterstützung in Samba, kernelbasiertem SMB/NFS, curl und anderen Projekten intensiv diskutiert, allerdings dürfte es bis zur Aufnahme in den Mainline-Kernel noch dauern
Hintergrund zur Entstehung von QUIC und die Grenzen von TCP
- QUIC wurde entwickelt, um verschiedene Probleme von TCP im heutigen Internet zu lösen
- Die durch den 3-Wege-Handshake entstehende Verzögerung beim Verbindungsaufbau, fehlende gute Multistream-Unterstützung und Head-of-Line-Blocking bei Paketverlust verschlechtern die Web-Erfahrung
- TCP-Metadaten werden unverschlüsselt übertragen, wodurch das Risiko von Informationsabfluss besteht; zudem filtern Middleboxes (Zwischengeräte) den Traffic anhand von Verbindungsinformationen, was zur Protokoll-Erstarrung (ossification) führt
- Auch Verbesserungsversuche bei TCP, etwa Multipath TCP, funktionieren in der Praxis oft nur dann zuverlässig, wenn sie sich als bestehendes TCP tarnen
Eigenschaften von QUIC und technische Vorteile
- QUIC läuft über UDP und kann Verbindungen schnell aufbauen, ohne dabei einen separaten 3-Wege-Handshake zu benötigen
- Das Übertragungsdesign mit Multistreaming sorgt dafür, dass Paketverlust nicht den gesamten Stream beeinträchtigt
- QUIC-bezogene Transportdaten sind stets Ende-zu-Ende verschlüsselt (TLS-basiert), sodass Zwischengeräte nicht auf interne Nachrichten zugreifen können
- In Netzwerkumgebungen, in denen UDP-Pakete passieren können, kann in der Regel auch QUIC normal arbeiten
Überblick über den QUIC-Integrationspatch im Linux-Kernel
- Im eingereichten Patch wird mit IPPROTO_QUIC ein neuer Protokolltyp eingeführt, sodass der bestehende socket()-Systemaufruf genutzt werden kann
- Ähnlich wie bei TCP können Aufrufe wie bind(), connect(), listen() und accept() verwendet werden, allerdings unterscheidet sich die weitere Verarbeitung
- TLS-Sitzungsverwaltung sowie Authentifizierungs- und Verschlüsselungsprozesse werden im Userspace verarbeitet; nach dem Verbindungsaufbau muss auf beiden Seiten der TLS-Handshake abgeschlossen werden, bevor Daten gesendet und empfangen werden können
- Nach der ersten Verbindung kann das Ergebnis der TLS-Aushandlung zwischengespeichert werden, wodurch erneute Verbindungen zwischen zwei Systemen deutlich beschleunigt werden
Herausforderungen bei der Leistung und Ausblick
- Die eingereichte QUIC-Implementierung im Kernel liegt bei der Leistung derzeit noch hinter bestehendem Kernel-TLS und TCP zurück
- Im Vergleich zu In-Kernel-TLS liegt der Durchsatz bei weniger als einem Drittel; selbst bei deaktivierter Verschlüsselung ist der Durchsatz gegenüber TCP um bis zu den Faktor 4 geringer
- Als Ursachen werden fehlende Unterstützung für Segmentation Offloading, zusätzliche Datenkopien im Sendepfad und die Header-Verschlüsselung genannt
- Künftig werden Leistungsverbesserungen erwartet, wenn Hardware-Offloading hinzukommt und die In-Kernel-Implementierung weiter optimiert wird
Stand der Einführung und weiterer Ausblick
- In verschiedenen Projekten wird In-Kernel-QUIC-Unterstützung intensiv diskutiert, darunter Samba-Server/Client, Kernel-SMB- und NFS-Dateisysteme sowie curl
- Der Patch umfasst rund 9.000 Zeilen und enthält derzeit nur Low-Level-Unterstützungscode. Die vollständige Implementierung ist in weiteren Patches angekündigt
- Die Diskussionen zu Code-Review und Merge haben gerade erst begonnen, daher dürfte es bis zum praktischen Einsatz noch dauern
- Wenn man den jüngsten Fall des Homa-Protokolls betrachtet, das über neun Monate hinweg elf Einreichungen für die Kernel-Integration benötigte, ist auch bei QUIC ein Einzug in den Mainline-Kernel wohl erst ab 2026 zu erwarten
1 Kommentare
Hacker-News-Kommentar
ssl_preread_server_namehinzugefügt, um Anfragen für bestimmte Domains perproxy_passan eine andere NGINX-Instanz weiterzuleiten.Die erste Instanz leitet dabei lediglich den rohen TLS-Stream weiter (einschließlich
proxy_protocol), und die zweite Instanz übernimmt die eigentliche TLS-Terminierung.Dieser Ansatz ist bei der Implementierung von Failover effektiv – fällt der primäre Pfad eines Servers aus, aktualisiert man den DNS-A-Record auf die NGINX-Instanz der Failover-Maschine, und diese Instanz leitet Anfragen für bestimmte Domains über einen separaten Pfad an das ursprüngliche Backend weiter.
Das ist praktisch, weil man nicht die gesamte TLS-Konfiguration duplizieren muss.
Allerdings funktioniert diese Methode nicht für HTTP/3.
HTTP/3 basiert auf QUIC, läuft über UDP, und beim Handshake wird das SNI verschlüsselt, sodass domainbasiertes Routing mit
ssl_preread_server_namenicht möglich ist.Ich frage mich, ob es eine Alternative gibt, um SNI-basiertes Routing für HTTP/3 zu unterstützen, oder ob man, wenn diese Funktion nötig ist, weiterhin bei HTTP/1.1 oder HTTP/2 über TLS bleiben muss.
In der Praxis hängt das Verhalten zwar von der Client-Implementierung ab (siehe den Status der HTTPS-Record-Unterstützung in Chromium in diesem Issue-Link), aber wenn eine QUIC-Verbindung fehlschlägt, fällt der Client transparent auf HTTP/1.1/2 zurück und beachtet auch den Alt-Svc-Header erklärt.
Bei geplantem Failover könnte man auch einfach keinen Alt-Svc-Header senden und warten, bis ein Timeout zur alternativen Instanz führt.
Wenn QUIC-Routing wirklich nötig ist, ist es zum Glück möglich, per Paketinspektion zu routen, weil die SNI-Information immer im ersten Paket enthalten ist.
udpgrm von Cloudflare könnte dabei als Referenz dienen, und das funktioniert, solange kein ECH (Encrypted Client Hello) verwendet wird.
Wenn ECH aktiv ist, muss der Router den Entschlüsselungsschlüssel besitzen, um eine Routing-Entscheidung treffen zu können, und protokollseitig lässt sich auch kaskadierendes Failover entwerfen.
Konkreten Code dazu findet man im udpgrm-Beispiel.
Wenn ein Angreifer Zugriff auf diesen Server hat, kann er ohnehin leicht neue SSL-Zertifikate ausstellen lassen, daher ist es vernünftiger, TLS einfach direkt dort zu terminieren, statt ein komplexes Failover-System zu bauen.
Ich persönlich konnte die Performance- und Zuverlässigkeitsvorteile von QUIC nie direkt reproduzieren.
Ich teste das seit Jahren immer wieder, deaktiviere es aber meistens aus Performance-Gründen.
Auch DNS-basiertes Failover braucht in der Praxis mehrere Minuten, bis es tatsächlich wirksam wird, und einfache Clients wie Browser führen das Failover oft nicht sauber durch.
Deshalb nutze ich stattdessen direkt einen
onerror-Handler, um einen zweiten Pfad zu laden.Zum Beispiel verwende ich für Ad-Tracking Code in dieser Art und biete die
fetch-API ebenfalls in einem solchen Wrapper an.Das ist deutlich effizienter als alles andere, was ich ausprobiert habe.
Selbst wenn der Browser bei einer QUIC-Verbindung scheitert (auch wenn sie im DNS angekündigt wird), fällt er automatisch auf HTTP/1 oder HTTP/2 über TLS zurück, sodass man dieselben bestehenden Failover-Mechanismen weiterverwenden kann.
Ein Designmerkmal von HTTP/3 ist gerade, dass Endpunktinformationen nicht bis hinunter zur TLS-Schicht offengelegt werden.
Ich persönlich halte das für einen Vorteil.
HAProxy kann rohes TLS proxyen, aber kein Routing anhand des Hostnamens durchführen.
Cloudflare Tunnel hat eine besondere Funktion, die hostnamenbasiertes Routing ohne TLS-Terminierung erlaubt, dafür muss das DNS allerdings ebenfalls über Cloudflare laufen.
Das wird gut in diesem passenden xkcd-Comic illustriert.
Ich frage mich, ob dieselbe Einschränkung auch in einer TCP+TLS-Umgebung mit Encrypted Client Hello gilt.
Ich vermute, die Antwort wäre nahezu identisch.
Diese Diskussion wirkt auf mich wie ein schrittweiser Versuch, genau solche Probleme zu lösen.
Künftig könnte es auch Hardware-Unterstützung auf Netzwerkkarten geben.
Aber da der Großteil des Internet-Traffics heute zwischen Mobilgeräten und Servern fließt, zeigen QUIC und HTTP/3 genau dort ihre Stärken.
Für andere Anwendungsfälle kann man weiterhin TCP verwenden.
Vermutlich wirkt es wie bisher mit mehreren Verbindungen, wird intern aber gecacht.
Ich persönlich fände es besser, explizit ein Connection-Objekt zu bekommen und darauf separat Streams zu öffnen, aber mit dem aktuellen Ansatz könnte ich auch leben.
Laut dieser Diskussion kann der Server, sofern es sich nicht nur um eine Erweiterungsfunktion handelt, nach dem Verbindungsaufbau ebenfalls neue Streams erzeugen.
Auf Client-Seite sind es in Wirklichkeit Streams, aber eine saubere, getrennte Abstraktion als „Verbindungen“ scheint schwierig zu sein, und grundsätzlich wirkt es so, als brauche man eine völlig neue API-Abstraktion.
Ich rechne eher mit einer Struktur, bei der man für jeden neuen Stream per
recvmsgeinen File-Descriptor erhält.Ich hätte gern etwas, das wie Mosh robust gegen Netzwerkprobleme ist, dabei aber alle Funktionen von OpenSSH unverändert bietet – SFTP, SOCKS, Port-Forwarding, State-Management, Roaming usw.
Ich frage mich, ob OpenSSH eine Kernel-Unterstützung überhaupt nutzen könnte.
Mosh ansehen
Vermutlich wäre es sinnvoller, stattdessen ein separates Login-Protokoll auf QUIC-Basis neu zu entwickeln.
Mehrere Ansätze befinden sich bereits im Prototyp-Stadium.
Nun heißt es aber, die aktuelle QUIC-Kernel-Implementierung sei im Vergleich zu Linux drei- bis viermal langsamer und der Performance-Abstand werde sich bald verkleinern.
Wenn Geschwindigkeit der Vorteil von QUIC ist, warum sollte man QUIC dann verwenden, wenn es in der Praxis langsamer ist?
Laut dem Autor des PR liegt ein Teil der Performance-Einbußen an der Protokollarchitektur selbst; gibt es also zusätzlich noch Probleme in TCP, die man separat beheben müsste?
Im Wesentlichen lässt sich das als „noch nicht optimiert“ zusammenfassen.
Beispiele sind fehlendes Segment-Offloading, zusätzliche Datenkopien im Transmission Path und Overhead durch Header-Verschlüsselung – alles Dinge, die sich voraussichtlich beheben lassen.
Das Benchmarking wurde hier zudem in einer sehr idealen Umgebung durchgeführt.
In der Realität, besonders im mobilen Umfeld, sind Netzwerke viel volatiler, sodass die strukturellen Grenzen von TCP stärker ins Gewicht fallen.
Tatsächlich werden viele QUIC-ähnliche Funktionen schon heute über TCP umgesetzt, etwa in HTTP/2.
Letztlich ist QUIC ein umfassender Networking-Stack, der oberhalb von OSI-Schicht 5 arbeitet, während TCP eher eine Engine auf Ebene von Schicht 3 ist – daher ist ein struktureller Vergleich schwierig.
Vor allem bietet QUIC schnelleren Verbindungsaufbau und Reconnects und stellt Sitzungs-Kontinuität auch bei IP-Wechseln sicher.
Die Multiplexing- und Non-Blocking-Stream-Struktur vereinfacht das Design von Protokollen in höheren Schichten grundlegend.
Wenn diese Architektur in den Kernel kommt, gibt es außerdem enormes Potenzial für Performance-Optimierungen.
Statt weiter mehrschichtige Lösungen auf den Grenzen von TCP aufzubauen, sollten wir im Alltag künftig häufiger fortschrittliche Basistechnologien wie QUIC verwenden.
Wenn Paketverlust auftritt, entsteht eine strukturelle Grenze, weil danach alle folgenden Übertragungen verzögert werden, bis die Wiederherstellung abgeschlossen ist (HOL Blocking).
Es geht also nicht einfach nur um Geschwindigkeit, sondern um geringere Latenz.
Siehe dieses technische Erklärungsdokument.
Ein wesentlicher Flaschenhals sind die Context Switches zwischen Kernel und Userspace.
Userspace-Networking (z. B. direkter NIC-Zugriff) vermeidet Kernel-Eintritte.
Umgekehrt kann auch Funktionsbereitstellung im Kernel-Space (z. B.
sendfile, In-Kernel-TLS, NIC-Offloading, direktes DMA von der Festplatte zur NIC) die Gesamtzahl von Context Switches und Datenkopien reduzieren.Aktuelle QUIC-Stacks nutzen derzeit die Vorteile beider Seiten nicht voll aus.
Paket-Ein- und -Ausgabe basiert auf Syscalls, und Datenkopien lassen sich nicht vermeiden.
Batch-I/O mit
io_uringkann die Zahl der Switches senken, aber nicht die Kopierarbeit selbst.Es gibt im Wesentlichen zwei Ansätze: Kernel-Bypass + DMA oder Modelle wie
sendfile/ktls, bei denen der Userspace außen vor bleibt.Eine QUIC-Kernel-Implementierung hat im Moment die Vorteile von beiden noch nicht.
Wenn man per DMA direkt in die NIC schreiben kann oder die Daten per Kernel-Syscall übergibt, ist der Performance-Unterschied erheblich.
Userspace-Networking ist nur dann wirklich überzeugend, wenn genau diese Privilege-Transitions und DMA-Strukturen gegeben sind.
Das wird im Wesentlichen nur von sehr großen Unternehmen (MOFAANG usw.) genutzt.
Theoretisch gibt es die Hoffnung, dass
io_uringdiese Vorteile allgemeiner verfügbar macht, aber in der Praxis ist das noch nicht so weit.Deshalb bleibt TCP/IP in den meisten Betriebssystemen im Kernel.
Ich dachte, der Kernel sei für Speicher, Hardware und Task-Management zuständig; sollten Protokolle oberhalb von IP nicht eigentlich im Userland verarbeitet werden?
Umgekehrt kann auch eine Trennung solcher Stacks in den Userspace in manchen Fällen Performance-Vorteile bringen.
TCP/UDP vermittelt im Kernel socketbasiertes Routing über Ports, sodass mehrere Programme TCP/UDP gleichzeitig verwenden können.
QUIC läuft über UDP, daher bleibt das Argument in der Diskussion weiterhin plausibel.
Hervorgehoben wird dabei, dass nur Protokolle direkt oberhalb von IP nicht einfach im Userspace laufen können.
Ich erwarte, dass das Internet dadurch künftig etwas schneller wird.
In Umgebungen wie 5G merkt man den Unterschied vielleicht nicht besonders, aber es ist trotzdem eine wertvolle Weiterentwicklung.
Ich finde die separate Link-Handshake-Struktur interessant.
Ich war ursprünglich davon ausgegangen, dass QUIC TLS komplett in sich selbst integriert, aber offenbar ist es etwas anders.
Trotzdem denke ich, dass sich die Latenz bei Spielen mit dieser Technik weiter senken lässt.
Wenn Computing-Ressourcen und Netzwerkeffizienz steigen, wächst auch die Nachfrage selbst.
Bei Spielen oder wissenschaftlichem Rechnen ist das egal, weil es dort um „bessere Ergebnisse“ geht,
aber im Web führt es durch mehr Werbung, Tracking und JavaScript oft zu gegenteiligen Effekten.
bind(),connect(),listen(),accept()usw. her, unterscheide sich danach aber durch die Nutzung der Syscallssendmsg()undrecvmsg().Ich hätte gern auch eine Erklärung dazu, warum man diesen Ansatz gewählt hat und warum nicht stattdessen eigene Systemaufrufe speziell für QUIC geschaffen wurden.