- Firefox のHTTPトラフィックの約20%が HTTP/3 を使用しており、これは QUIC および UDP 上で動作する
- 既存のネットワークI/O層である NSPR を Rustベースの quinn-udp に置き換えることで、性能とメモリ安全性 を強化
- 各OSごとに最新のシステムコール(multi-message、segmentation offloading など)を積極的に活用し、性能最適化を適用
- Windows および macOS では一部機能が互換性やドライバーの問題などで制限されたが、Linux では最適な性能を確認
- QUIC ECNサポートとUDP I/O に関するさまざまなプラットフォーム別の試行錯誤やバグ修正の経験が、今後のプロジェクトやオープンソースエコシステム にも役立つ見込み
動機
- Firefox のHTTPトラフィックの約20%が HTTP/3 を使用しており、これは QUIC を通じて動作し、さらに UDP 上に実装されている
- Firefox は歴史的にネットワークI/Oに NSPR ライブラリを使用してきたが、UDP I/O関連機能は 古く制限が多い(主要な関数は
PR_SendTo、PR_RecvFrom)
- OS は近年、マルチメッセージシステムコール(例:
sendmmsg、recvmmsg)や セグメンテーションオフロード(GSO、GRO)といったネットワーク最適化を提供している
- これらの技術は UDP I/O の性能を大幅に向上させられる
- Firefox が 既存のUDP I/Oスタックをモダンなシステムコールに置き換えて こうした利点を得られるかを模索
概要
- プロジェクトは2024年半ばに開始され、目標は Firefox の QUIC UDP I/Oスタックを、サポート対象のすべてのOSでモダンなシステムコールにより再構築 すること
- 性能改善に加えて、UDP I/Oに メモリ安全性が保証されたRust を活用し、セキュリティ向上も図る
- QUIC自体はすでにRustで実装されているため、Rustベースの quinn-udp ライブラリ 上で開発を進めた
- OS間のシステムコール差異は開発難度を高めたが、quinn-udp により開発速度は大きく改善
- 2025年半ば時点で大半のFirefoxユーザーへの適用が進行中で、性能ベンチマークでは 最大4Gbit/sへ大幅に向上 する結果を示した
UDP I/O構造とモダンな最適化方式
単一データグラム送信
- 従来方式は
sendto と recvfrom を使用し、一度に 単一のUDPデータグラム のみ送受信
- ユーザー空間とカーネル空間の切り替えコストはデータグラム単位で発生するため、大容量トラフィック環境では 非効率
- 例: 1500バイト未満のパケットを毎秒数百Mbit以上で送るにはかなりのオーバーヘッドが発生
複数データグラムのバッチ送信
- Linux など一部のOS では
sendmmsg および recvmmsg のようなマルチメッセージシステムコールをサポート
- 複数のデータグラムを一度に送受信することで、オーバーヘッドを大幅に削減可能
単一の大容量セグメント化データグラム
- GSO(送信)、GRO(受信) などのオフロード技術により、大きなUDPデータグラムをOSまたはNICで自動的に分割 して送信
- ネットワークインターフェースがパケット単位の分離、チェックサム計算、ヘッダー付与を処理
- これによりアプリケーション側は たった1回のシステムコールで多数の実パケットを処理可能
- GSO有効時は Wireshark など一部のネットワークツールでパケット解析のサポートが不十分
Firefox の NSPR 置き換えプロセス
- まず 単一データグラム送受信 構造で quinn-udp により NSPR を置き換え
- QUIC実装のデータグラム処理パイプラインを バッチ送受信およびセグメンテーション対応 にリファクタリング
- multi-message コール、segmentation offload コールをいずれも状況に応じて活用
- プラットフォーム別の例外処理や各種I/O改善機能を追加
プラットフォーム別の詳細
Windows
- Windows は
WSASendMsg / WSARecvMsg を提供し、従来のMTUサイズのデータグラムまたは大型のセグメント化データグラムをサポート
- Linux の GSO / GRO に対応する Windows の USO(送信) / URO(受信)
- 当初は単一データグラムコールのみを使っていて問題なかったが、URO有効時に特定環境(例: Windows on ARM + WSL)で QUICパケット長を判定できない ためページ読み込みが失敗するバグが発生
- USO / 送信も使用したが、Firefox の Windows インストール環境で パケット損失の増加やネットワークドライバーのクラッシュ などの副作用が見つかった
- 現在の Firefox では URO / USO を無効化した状態 を維持し、追加のデバッグを進めている
macOS
- macOS では既存の
sendto / recvfrom の代わりに sendmsg / recvmsg を用いて quinn-udp を導入
- セグメンテーションオフロード機能は有効化されていない
- 代わりに公式には文書化されていない
sendmsg_x / recvmsg_x により バッチ送信 をサポートし、quinn-udp に非公式に適用
- Apple が将来的にこれらの呼び出しを削除する可能性があるため、デフォルト有効化はせずテストのみ実施 し、実際のリリースには含めなかった
Linux
- sendmmsg / recvmmsg および GSO / GRO の両方をサポートし、quinn-udp は送信時に GSO をデフォルトで優先
- Firefox は 接続ごとに個別のUDPソケットを使用 してプライバシー強化を図る(4-tuple 区別)
- この構造では セグメンテーションオフロードの利点が最大化 され、sendmmsg / recvmmsg のクロス送信の利点は限定的
- ネットワークサンドボックスや実行時の GSO サポート確認などの小規模な変更を除き、大きな困難なく導入に成功
Android
- Android は Linux と異なり、システムコール処理経路やセキュリティフィルター(例: seccomp)が異なる
- x86ベースの Android 5 など 非常に古いプラットフォームのサポート、
socketcall の回避、エラー処理など、さまざまな互換性問題が存在
- 一部環境では ECN ビットを有効にした送信呼び出し時にエラー(EINVAL)が発生し、quinn-udp で 再試行およびオプション無効化戦略 を適用
- Quinn コミュニティでのさまざまな改善の恩恵により、Firefox も自動的に改善効果を享受 できる
ECN(明示的輻輳通知)サポート
- モダンなシステムコール導入により ancillary data(付加データ)の送受信をサポートし、QUIC ECN をサポート可能に
- 小規模なバグはあったが、Firefox Nightly では 半数以上の QUIC 接続が ECN outbound 経路で動作
- L4S などの新技術が注目されるにつれ、ECNサポートの重要性と活用度が上昇
結論の要約
- Firefox の QUIC UDP I/O層を quinn-udp ベースの Rust 実装 に置き換え、性能と安全性を同時に確保
- 古いシステムコールではなく 各OSごとの最新I/Oシステムコール を活用することで、スループット向上と ECN サポートが可能に
- Windows など一部の最適化機能は互換性問題のため追加改善が必要
- QUIC の利用率が継続的に増えるにつれ、今後も OS / ドライバーレベルのサポートは発展し続ける見込み
1件のコメント
Hacker Newsの意見
記事の核心は途中に隠れている
ここでの改善は実際、超高速(100Gb/s以上)を実現するには必須ではあるが、4Gb/sは正直それほど速くない
500MB/sなので、これはどこかに深刻に遅いボトルネックがあることを意味する
カーネルのコンテキストスイッチが1us台とのことだが、実際システムコールとしては高い方だ
ただし、1パケットあたり平均約500バイトしかなくても500MB/s、つまり4Gb/sは達成できる
以前の1Gb/sはパケットサイズがもっと小さかったときの話で、UDPパケットを単にNICバッファに押し込むだけなら、メモリコピー速度でも十分可能な水準だ
暗号化が遅いと言っても、実際にはそうではない
例えばIntel i5-6500は1729MB/sのAES-128 GCM速度を出したことがある
今のCPUならコアあたり3-5GB/s、つまり25-40Gb/sも可能で、ここで言われている4Gb/sは低すぎる数値だ
(AES-128 GCM性能の参考リンク)
システムコール待ち時間が高いと言っていたが、その原因はSpectreとMeltdown対策かもしれない
TCPにはパスバインディングがあるが、UDPにはないため、経路設定に違いが大きい
暗号化が遅いというのは、小さなPDU(プロトコルデータユニット)ではその通りだ
大半は大容量TCPフレーム基準で最適化やベンチマークが行われているため、実際には小さいパケットで状態設定コストが目立つ
tight loopでマイクロベンチマークを回せば数値はよく出るが、実際のランダム性のある環境ではキャッシュ活用効率も下がり、1KB以下のパケットでは効率が大きく落ちる
さらに追加のフレーミングオーバーヘッドや、帯域外データの妥当性検証などもかなり高コストで動作する
UDPバッファメモリもデフォルト値が不足していて、実運用では問題が多い
TCPでは運用しながらバッファサイズを増やし続けてきたが、UDPは90〜00年代の保守的な値にとどまっている
本当に必要なAPIは、fdをforkして
connect(2)とルートバインディングを完全にサポートし、その後はsubmission queueベース(uring、rioなど)であるべきだ暗号化の面では、KDFアプローチが状態コストを大きく減らせる
PSP方式は一部ベンダーは認めているが、IETFなどではかなり拒否されており、広く普及していない
ベンダーによる大規模同時実行テストでは、既存のTLS系よりはるかに高いスケーリング値が出ている
ベンチマークしたCPUがどのクラスなのかはまったく言及されていない
そして暗号化オーバーヘッドはQUICプロトコル自体の処理コストだ
QUICは暗号化オフロード(ハードウェア処理)がTCPに比べて弱く、TCPはkTLSオフロードである程度NIC側で処理できる
こういう技術コンテンツは本当に満足度が高かった
Mozillaのすべての技術資料が、こうして実務エンジニアがきちんと書いた深い内容だったらいいのにと思う
楽観論(alegria)みたいなもの抜きで、読む価値が高い
なぜAndroid 5をいまだにサポートしているのか分からない
リリースから10年以上経っているし、その端末を使っているユーザーはさらにレガシーだ
今どきのWebは重すぎて、こういう古い端末ではまともにブラウジングすることすら難しいはずなのに、わざわざ対応する理由が気になる
たぶん昔のOnePlusのような端末を修理して充電器につないだまま使い、LineageOSのような定番ROMすら入れずに代替アプリストアでFirefoxを使ってみるハッカーくらいだろう
現実的には、全体の開発速度を遅らせるコストになっている
"The map download struggle"というFactorio開発ブログにもこれに関連する興味深いエピソードがあるのでおすすめ(関連ブログ記事)
実際にネットワーク問題を扱ったことがある人なら、mystery packet runtsのせいでより共感できる内容だ
ほとんどのネットワーク機器はこうしたパケットの処理がうまくない
UDPやQUICベースのトラフィックは、ある程度以上の大規模クラウド環境でなければ簡単に攻撃に振り回される
このため、小規模または自前運用のホスティング事業者はますます運営が難しくなり、大規模トラフィック処理能力のあるところだけが残る
そのため多くのLAN環境では、UDPトラフィックの大半をドロップし、必要な部分だけをレート制限つきで処理している
Mozillaバグトラッカー
macOSとFedoraで、CloudflareがホストしているサイトにFirefoxでアクセスすると、いまだに同じ現象が発生している
WindowsとMacOSにもGSO/GRO(大容量ネットワークパケット処理)に似た機能があることを今回初めて知った
ただ、実際にはバグが多いらしく残念だ
GSO/GROだけにバグがあるわけではないだろうという気もする
UDP GSO/GROが構造的にどう動くのか説明できる人はいるか、という質問だ
UDPは順序のないパケットなのに、QUICの1パケットが複数のUDPパケットに分割されるとき、ヘッダーにシーケンス情報もないのに受信側がどうやって順序を合わせて結合するのか気になる
カーネルが複数のデータグラムを1つの構造体に入れ、各層の間で境界(たとえば
sk_buff内のdata fragments)を維持したまま渡すという考え方だ正確な専門家ではないが、この仕組みがどう動くのか調べる中でこの記事を参考にした
「私たちはQuinnプロジェクトのUDP I/Oライブラリであるquinn-udpの上で開発を始め、それによって開発速度が大きく上がった」と書かれていたが
それならQuinnプロジェクトに支援を行ったのだろうかと気になる
(Quinn支援リンク)
資金支援について直接聞いてみたところ、MozillaのSenior Principal Software Engineerが「Mozillaにはお金がないんです」と答えてくれた
ただしコードはものすごくたくさん貢献してくれていて、本当に感謝している
(私はQuinnのメンテナーだ)
「支援したのか?」という質問に対して、Mozillaはオープンソース支援をするよりもCEOの報酬にさらに数百万ドル使うのが流儀だ、という意見も出ていた
フラッグシップ製品(Firefox)さえ崩れかけているのに、という文脈だ
コードなど、ほかの形でどんな貢献があったのか気になる
sendmmsg/recvmmsgが「最新」と呼ばれているのは驚きだ実際にはかなり長い間存在してきたシステムコールだ
Linuxの話題なら
io_uringにも触れられるかと思ったが、なくて残念だったio_uringには複数のUDPダイアグラムを一度に処理する、本当の意味でのバッチ機能はないせいぜい、
sendmsgやrecvmsgを複数まとめて要求する程度だGSO/GROこそが答えだ
sendmmsg/recvmmsgはすでに非常に古い技術で、カーネル開発者の中にはもう廃止したいと思っている人もいる(関連GitHub議論)