世界を動かす技術を、日本語で。

カーネル向けのQUIC

概要

  • LWN購読者の特典 とサイト支援の重要性
  • QUICプロトコル の特徴とLinuxカーネルへの統合状況
  • TCPの課題 とQUICによる解決策
  • カーネル実装の 現状と今後の展望
  • パフォーマンスや対応アプリケーション の現状と課題

LWN購読者の主な特典

  • LWN購読 の最大の意義は、サイト運営の継続支援
  • 購読者は 全コンテンツへの即時アクセス が可能
  • 追加機能 の利用権
  • サイトの発展に 直接貢献

QUICプロトコルの概要とLinuxカーネル対応

  • QUIC は2013年に初登場したトランスポート層プロトコル
  • TCPの遅延や改良困難 など現代インターネットの問題に対応
  • 三者間ハンドシェイク廃止 による高速な接続確立
  • UDP上に構築 され、複数ストリーム伝送やヘッドオブラインブロッキング回避
  • エンドツーエンド暗号化 でメタデータ漏洩やプロトコル硬直化を防止

TCPの課題とQUICの利点

  • TCPは改良困難 で、ミドルボックスによるプロトコル硬直化が深刻
  • マルチパスTCP などの改良も困難
  • QUIC はUDPベースで柔軟な設計
    • TLSによる認証・暗号化 を標準装備
    • ストリームごとの独立性 でパケット損失の影響を最小化

カーネル実装の現状と技術的詳細

  • Xin Long氏 によるLinuxカーネル向けQUICパッチが投稿
  • IPPROTO_QUIC という新しいプロトコル型を追加
  • socket(), bind(), connect(), listen(), accept()など TCP類似のAPI
  • TLSハンドシェイク はユーザ空間で実施
    • libquicやtlshd (ktls-utilsプロジェクト)が利用可能
  • TLSネゴシエーション結果のキャッシュ で再接続高速化

パフォーマンスと課題

  • 現状のカーネル実装は期待ほど高速でない
    • in-kernel TLSと比較しスループットが約1/3
    • TCPとの比較では4倍以上の差
  • 主な要因
    • セグメンテーションオフロード未対応
    • 送信経路での追加データコピー
    • ヘッダー暗号化によるオーバーヘッド
  • 今後の見通し
    • ハードウェアオフロード対応で性能向上期待
    • カーネル実装の最適化進展

アプリケーション対応と今後の展望

  • Sambaサーバ・クライアント 向けQUICサポートのプルリクエストが承認済み(2024年7月17日)
  • SMB・NFSファイルシステム への対応も検討中
  • curl 向けカーネルQUIC対応リポジトリも登場
  • 今後多くのアプリケーションで採用拡大 の見通し
  • 現在は約9,000行の低レベル実装のみ
    • 残りの実装も今後投稿予定
    • レビューやマージには時間がかかる見込み
    • 最短でも2026年以降のメインライン統合 が予想

まとめ

  • QUICのカーネル統合 は着実に進行中
  • パフォーマンスやアプリ対応 は今後の最適化と拡大に期待
  • LWN購読 による情報取得とコミュニティ支援の重要性

Hackerたちの意見

QUICの欠点についての記事を思い出したよ: https://www.reddit.com/r/programming/comments/1g7vv66/quic_i... これがその問題を解決する方向に進んでいるみたいだね。将来的にはハードウェアサポートも受けられるんじゃないかな。

QUICは機械間トラフィックのようなユースケースにはあまり向いていないね。でも、今のインターネットのトラフィックのほとんどはモバイルフォンからサーバーへのもので、そこでQUICとHTTP 3が輝くんだ。他のユースケースではTCPを使い続ければいいと思う。

QUICは速さを目指しているけど、パッチシリーズに含まれているベンチマーク結果は、提案されたカーネル内実装がそれに達していないことを示している。カーネル内QUICとカーネル内TLSの比較では、後者がいくつかのテストでほぼ3倍のスループットを達成している。暗号化を無効にしたQUICと通常のTCPの比較はさらに悪く、TCPが場合によっては4倍以上の差で勝っている。マジでやばいな。ユーザースペースのQUIC実装もこんなに遅いのか知ってる人いる?

そうなんだよね。さらに悪いことに、TCPトラフィックの混雑に直面すると、何もなくなっちゃうのを見たことがある。もしQUICが本当に未来のプロトコルなら、カーネルに移行するのはいいことだと思う。こんなに大きなユーザースペースの実装をあちこちに提供して、なおかつ古き良きTCPに勝てると思ってるのは狂気だよ。NICレイヤーまで最適化が必要になるかもしれないし、ミドルボックスもね。あ、UDPのCPUコストについてはまだ触れてないけど。逆に、TCPはジムでいつもダボダボの服を着てる静かな奴みたいなもので、誰も見てないときにベンチで4プレートやるんだ。過小評価しちゃダメだよ。この教訓を学ぶのに数ヶ月無駄にしたから。

「速い」という主張は単に違うと思う。QUICは以下のようにして速くすることを目指している: - レイテンシの低いハンドシェイク - ユーザーとサーバーの間の悪い「ミドルウェア」ボックスを避ける - ユーザーのIPアドレスが変わったときに接続をリセットしない - ヘッドオブラインブロッキングや多くの接続の立ち上げコストを避ける - 悪い混雑制御アルゴリズムを避ける - おそらく他にもいろいろ これらは、ユーザー(多くはモバイルデバイス)とサーバーの間でよく見られるネットワーク状況でうまく機能するためのものだと思う。QUICはデータ送信時のOSオーバーヘッドを減らすことで速くなることを目指しているわけじゃないし、オペレーティングシステムがこのフローに対してより最適化され、ハードウェアがより多くの作業をオフロードできるようになるまで、一般的には長い間遅くなることを期待すべきだと思う。もしあなたがGoogleなら、そのために特化したネットワークカードやドライバー、ソフトウェアに投資するつもりだろうね。

TCPがどれだけよく設計されているかの興味深い証拠だね。

QUICのパフォーマンスはバッチ処理の使い方に気をつけないといけない。UDPパケットを無造作に使うと、つまりシステムコールごとに1つのQUICパケットを送ると、すごくオーバーヘッドがかかるんだ。カーネルがどのインターフェースを使うか決めて、バッファにキューイングするたびにね。TCPのように使って、たくさんのデータをバッチ処理して1回の「呼び出し」でパケットをキューイングすると、すごく助かる。同様に、カーネルのWireGuard実装はトラフィックをバッチ処理しないから、wireguard-goより遅くなることがある。現代のハードウェアが提供する速度では、効率的にするためにベクタードI/Oを使う必要があるね。

そうだね。msquicは最高のパフォーマンスを誇る実装の一つで、約7Gbpsを達成してる。

TCPの方がQUICよりもベンチマークでずっと良いパフォーマンスを発揮すると思うよ。実際にローミングLTE接続でベンチマークをやって、結果を持ってきてほしいな。実際のベンチマークコードを見ないと、その特定の結果に気を使うべきかどうかも判断しづらいし。もし目的が内部または公共のインターネットでAからBに大量のバイトを送ることなら、TCPを上回るものはほとんどないと思う。数十年かけてTCPを最適化してきたからね。HOLブロッキングが問題でなければ、HTTPをTCPの上で使い続ければいいんじゃないかな。

個人的には、Googleのプロプライエタリなクソみたいなものだから、避ける理由としては十分だと思う。それが実際に良くないっていうのも、さらに説得力のある理由だね。

これは明らかな誤解だと思う。理由は記事自体に書いてあるけど。QUICの魅力は、硬直化に対して免疫があること、プロトコルのパラメータを変更するのにLinuxのメンテナーに頼む必要がないことだよ。

硬直化は「Linuxのメンテナー」の決定から生じるわけじゃない。ミドルボックスを設計、販売、展開している人たちを見ないといけないよ。

私の意見では、サーバー側はカーネルに置くべきだと思う。そうすれば、カーネル内TCPに近いパフォーマンスが得られるし、硬直化もそれほど問題にならない。サーバー側のカーネルを修正するのは「簡単」だからね。一方で、クライアント側はユーザースペースにいるべきだ。クライアントのカーネルを修正するのは難しいから。もしあなたがGoogleなら、Androidクライアントがカーネル内プロトコル処理を定期的に更新できるモデルに向けて取り組むかもしれないけど、どうもGoogleはそれをやる気も能力もないみたい。AppleやMicrosoftはほとんどのユーザーに優先的なカーネル更新を迅速に提供できるし、Appleはクライアントに使ってほしいもの(IPv6、MP-TCP)をサポートするようネットワークに影響を与えることもできる。もしTCPの両側での混雑制御に満足していて、http/1のように複数のTCP接続を開くことを厭わないなら、QUICはTCPにはない再送制御を提供するけど、それだけでは魅力的とは言えないと思う。そう、TCP最適化を行うミドルボックスにはまだ硬直化がある。私の情報は古いかもしれないけど、IPv6では誰もそれをやっていないという印象があったから、v6への推進はNATや特にCGNATを避ける手段でもあり、ネットワークプロバイダー(コスト削減)やサービス(ストレス軽減)にとってもメリットがあると思う。

QUICをカーネルに入れると、QUICがかなり硬直化しちゃうと思う?もしそうなら、実際に必要なシステムコールのパフォーマンスペナルティにはどう対処するつもり?君の懸念は理解できるよ。Linuxカーネルはユーザースペースのソフトウェアよりも動きが遅いし、ミドルボックスはカーネルを更新しないこともあるからね。

プロトコル自体は、実装がどうであれ、硬直化に対して抵抗力があるんだ。これは主に暗号化を使うことで達成されていて、プロトコルの重要で必須な部分になってる。エンドポイント間でプロトコルの露出を最小限に抑えるのが目的で、残りは暗号化されてるから、「ミドルボックス」がパケットを見て自分の解釈で変なことをするのを防げるんだ。エンドポイントは好きなことができるし、硬直化は起こり得るけど、インフラレベルでの硬直化を防ぐのには役立つ。サーバーのLinuxカーネルを更新するのは、ネットワークのバックボーンを構成するプロプライエタリハードウェアを変更するよりも簡単だよ。QUIC/IPを直接使う代わりにUDPを使うのも、硬直化防止の手法で、アプリはQUICのカーネル実装に関係なくUDPとユーザースペースのライブラリを使えるからね。理論的には生のソケットでもできるけど、ポートがないから自分のために全体のインターフェースが必要で、しばしばrootアクセスも必要になるから、もっと問題が多い。

https://microsoft.github.io/msquic/

https://lwn.net/ml/all/cover.1751743914.git.lucien.xin@gmail...

カーネルでの使用については分からないけど、OpenSSHがQUICをサポートしてくれたら嬉しいな。そうすればMoshの利点を享受しつつ、SFTPやSOCKS、ポートフォワーディング、状態テーブルやキープアライブの問題が少なく、ローミングサポートなどOpenSSHの機能も全部使えるから。OpenSSHはカーネルのサポートを活用できるかな?

SSHは、QUICで暗号化やマルチプレクサのレイヤーを置き換えるためにかなりの作業が必要だね。QUICログインプロトコルをゼロから作るのが良さそう。いろんなプロトタイプの状態で、いくつかのアプローチがあるみたいだよ。

OpenSSHはOpenBSDのプロジェクトだから、LinuxのAPIはあまり興味ないかもしれないけど、もちろん間違ってるかもしれないね。

ちょっと混乱してる。過去10年くらいの革命は、ネットワークスタックをユーザースペースに移動させてパフォーマンスを向上させることだと思ってたんだけど。

ハードウェアアクセスのためのモード切替が遅いね。TCP/IPはWindowsとLinuxのカーネル内に残ってる。

パフォーマンスは、ポーリングにコアを専念させることで得られるもので、ユーザースペースからは来ないよ。

その通りだけど、アプローチが二つあるからちょっと混乱するよね。どちらのアプローチもコンテキストスイッチやシステムコールを排除することでパフォーマンスを向上させるって言えると思う。1. カーネルバイパスとDMA、パケット処理にCPUを専念させる技術がパフォーマンスを上げる。2. 「データプレーンからユーザースペースを排除する」って考えると、sendfileやktlsのようなもののパフォーマンスが向上する。君の言う通り、カーネル内のQuicはこのどちらの利点も持ってないみたいだね。

それに関しては、ユーザースペースがネットワークデータを直接取得するんだ。syscallを関与させないと思うけど。これはエンドユーザー向けのソフトウェアにはやらないことで、MOFAANGみたいなところだけが必要とするものだね。理論的には、io_uringのようなものが全体にこれらの利点をもたらすはずだけど、まだ実現はされてない(でも、楽観的でいるよ)。

カーネル内のネットワーキングはすごく速いよ。ASICだとさらに速い。ネットワークスタックがユーザースペースに移動したのは、GoogleがTCP自体を置き換えたかったから(TLSもアップグレードしたかった)だけど、ブラウザのことしか気にしてなかったから、スタックをブラウザに入れちゃったんだよね。それで問題解決。

バイトをNICのバッファにオフロードする必要があるよ。DMAのようなことをして、NICが読み取るために特権スペースにバイトを書き込むか、syscallの境界を越えてカーネルにバイトをNICのバッファに書き込ませる必要がある。syscallの境界を越えると、メモリ空間と特権リングの切り替えによる大きなパフォーマンスペナルティが発生するから、ユーザースペースのネットワーキングは特権の変更に対処しなくて済む場合か、DMAがある場合にしか意味がないんだ。

ほとんどのQUICスタックは、カーネル内のUDPに基づいて構築されてるんだ。トラフィックがカーネルやユーザースペースを通るのを避けられれば、パフォーマンスがかなり向上するよ。ネットワーキングをユーザースペースに移動させて、NICキューに直接アクセスできるようにすれば、カーネルにコンテキストスイッチする必要がなくなる。あるいは、ネットワーキングをカーネルスペースに移動させる方法もあるよ。sendfileみたいな機能を使えば、TCPアプリケーションがカーネルにファイルを送信させることができて、内容をユーザースペースにコピーする必要がなくなる。カーネル内でTLSを使ってsendfileを使えば、ユーザースペースへのコピーを省略できるし、NICベースのTLSならカーネルがディスクからデータを読み取る必要もない。NICベースのTLSで、ディスクがNICバッファにDMAできるなら、データがメインメモリに触れる必要すらない。だけど、ほとんどのQUICスタックはどちらの利点も得られてないんだ。syscallsを通じてパケットを読み書きしていて、パケット化はユーザースペースで行われてる。sendfileを使ってコンテキストスイッチやコピーを省略するチャンスはないよ。io_uringなどを使ったバッチ処理はコンテキストスイッチには役立つけど、コピーを防ぐことはできないかもね。

最近、特定のドメインへのリクエストを別のNGINXインスタンスにproxy_passするために、NGINXの設定にssl_preread_server_nameを追加しなきゃいけなかったんだ。この設定では、最初のインスタンスが生のTLSストリームをそのまま転送して(proxy_protocolを前に付けて)、二つ目のインスタンスが実際のTLSの終端処理をするんだ。このアプローチはフェイルオーバーメカニズムを実装するのにうまく機能するよ:サーバーへのデフォルトパスがダウンしたら、DNSのAレコードを更新して、NGINXを動かしているフォールバックマシンを指すようにできる。フォールバックインスタンスは、特定のドメインへのリクエストを元のバックエンドに別のパスでルーティングできるから、完全なTLS設定をローカルで複製する必要がないんだ。ただし、この方法はHTTP/3では使えない。HTTP/3はUDP上でQUICを使って、ハンドシェイク中にSNIを暗号化するから、ssl_preread_server_nameを使ってドメイン名に基づいてルーティングすることができなくなる。HTTP/3でこのようなSNIベースのルーティングをサポートするための代替手段は何かある?この挙動が必要な設定には、HTTP/1.1やHTTP/2をTLSで使い続けるのが推奨される解決策なの?

うーん、それはいい質問だね。TCP+TLSのEncrypted Client Helloにも同じことが当てはまると思うんだけど、どうかな?おそらく、答えは両者で同じか似たようなものになるだろうね。

残念だけど、これは「バグではない」カテゴリーに入ると思う。エンドポイントをTLSエンドポイントまで隠すのはHTTP/3の特徴なんだ。* 実際、これは特徴だと思ってるけど、https://xkcd.com/1172/ も認めてるよ。PS. HAProxyは生のTLSをプロキシできるけど、ホスト名に基づいて直接はできない。Cloudflareトンネルは、TLSを終了させずにホスト名でプロキシできる特別な機能があると思うけど、DNSプロバイダーとして彼らを使う必要があるね。

QUICをサポートしているクライアントは通常、HTTPS DNSレコードもサポートしてるから、低優先度のレコードをフェイルオーバー用に使えるよ。クライアントがそれを処理できる可能性があるからね。(例えば、host -t https dgl.cxを見てみて。)理論上はそうなんだけど、クライアントがそれをやってくれるとは限らない(Chromiumが実際にどれだけHTTPSレコードをサポートしてるか見てみて[1])。でも一般的に、QUICが何らかの理由で失敗した場合、クライアントは透明にフォールバックするし、Alt-Svc[2]ヘッダーも尊重されるよ。もし計画的なフェイルオーバーなら、Alt-Svcレコードの送信を止めて、代替がタイムアウトするのを待つこともできるけど、厳密には必要ない。もし本当にQUICをルーティングしたいなら、SNIは常に最初のパケットに含まれてるから、最初のパケットを検査することでフローをルーティングできるのがいいところだね。Cloudflareのudpgrm[3]を見てみて(これだけでは他のマシンにプロキシするには不十分だけど、基本的な部分はあるよ)。Encrypted Client Hello(ECH)がない場合、クライアントハロー(SNIを含む)は既知のキーで暗号化されてる(これはQUICのバージョンを知らないミドルボックスが壊すのを防ぐため)。だから、復号することは可能で、udpgrmのコードを見てみて[4]。ECHがある場合、「ルーター」はECHを復号するためのキーを持っている必要があって、それをインラインで復号して判断を下すことができる(これはTLSキーとは異なり、フォールバックHTTPSレコードを使って非フォールバックルートとは異なるキーを使うこともできるけど、ブラウザが現在それをサポートしているかは別の問題だね。ただ、プロトコル上では可能だよ)。これは、ECHを使ったフォールバックがHTTP/2とTCP接続でサポートされる方法に似てる。

フェイルオーバーの状況では、QUICのフェイルオーバーは全く気にしない方がいいと思う。ブラウザがQUIC接続を確立できない場合(DNSで広告されていても)、HTTP1/2 over TLSを試みるからね。そうすれば、問題がなければ同じフェイルオーバーメカニズムを使えるよ。

記事ではACKについて触れてなかったね。プロトコルにACKがないのは意味があるのか、アプリケーション層に任せるべきなのか、ずっと疑問に思ってた。アプリケーション層がこれを確実にする必要があると思うから、下の層でこれをサポートすることにどれだけのメリットがあるのか分からないな。

いい感じだね。QUICは多くの人にとって本当にゲームチェンジャーだと思う。これでインターネットがちょっと速くなるはず。5Gのおかげであまり気にしないかもしれないけど、それでも価値はあるよね。別の2回のハンドシェイクがあるって聞いて、QUICがTLSを埋め込んでると思ってたけど、どうやら間違ってたみたい。

質問があるんだけど、TCPのボトルネックはハンドシェイクだと言われてるよね。でも、それは接続を再利用したり、マルチプレクシングすることで解決できるんじゃない?今の実装はLinuxの実装より3〜4倍遅いし、パフォーマンスの差は縮まる見込みだよ。QUICの利点としてスピードが謳われてるけど、実際には遅いなら、なんでこのプロトコルにこだわるの?PRの著者自身もスピードの問題をプロトコル設計に起因してるって言ってるし、TCPに他に直すべき問題はあるの?

それはただのボトルネックの一つだね。もう一つの問題はヘッドオブラインブロッキングだよ。TCP接続でパケットロスが発生すると、その後に送信されたものはロスが修復されるまで配信されないからね。

記事では、QUICが現在遅い理由がいくつか挙げられてるね。ほとんどは「まだ最適化してないから」って感じみたい。> Longは、この違いのいくつかの理由を挙げていて、QUIC側でのセグメンテーションオフロードサポートの欠如、伝送経路でのデータコピーの追加、QUICヘッダーに必要な暗号化が含まれてる。これらの理由は、解決可能なものに見えるね。ここでのベンチマークは、完璧なネットワーク条件でのもので、言ってみればドラッグレースみたいなもの。モバイルだと、ネットワークの変動がもっと大きくなるから、そこでTCPの設計の限界がより明らかになるよ。TCP自体は、QUICのようなことをするために上にプロトコルが走ることが多い。HTTP/2がその一例だね。だからQUICとTCPを比べるのは、車の速さと、車輪のついたフレームにエンジンを取り付けた速さを比べるようなものだよ。QUICはOSIネットワークスタックの上の方に位置していて、レイヤー5以上。対してTCP+TLSはレイヤー3。システム設計が少ないんだ。QUICは接続が速く、特に再接続が速いっていう利点もある。IPモビリティもあって、モバイルでIPアドレスが変わった場合(よくあることだよね)、QUICはクライアントが次のパケットを送るまでセッションを維持できる。これは本当に考え抜かれた素晴らしい進歩で、いろんな面で劇的に良くなってる。複数のノンブロッキングストリーム(SCTPのようなもの)があることで、高レベルのプロトコル設計が引き受ける範囲が大幅に減るんだ。そして、そのマルチストリーミングがカーネルにあることで、TCPでは享受できない深い最適化が可能になる。もう古いボロボロのTCPを引きずり回すのはやめよう。変な手作りのものをその上に乗せるのもやめて、もっと良いスタート地点が必要だよね。QUICは本当に魅力的だよ。

「利点」は、サーバー提供の接続IDによるトラッキングだよ。 https://www.cse.wustl.edu/~jain/cse570-21/ftp/quic/index.htm...

TCPのボトルネックはハンドシェイクだと言われてる。でも、それは接続を再利用することで解決できる。 まだ存在しない接続を再利用することはできないよ。これの多くは、全体的な速度ではなく、レイテンシを減らすことに関することなんだ。