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

RustによるFirefox向けの高速UDP入出力

概要

  • FirefoxのHTTPトラフィックの約20%が HTTP/3 (QUIC/UDPベース)を使用
  • 従来の NSPRベースUDP I/O から、 Rust/quinn-udp による最新システムコール活用へ移行
  • パフォーマンス向上セキュリティ強化 を両立
  • OSごとの最適化・課題とその対応
  • 本プロジェクトの知見は他プロジェクトにも有用

FirefoxのUDP I/O最適化プロジェクト

  • HTTP/3(QUIC) の普及により、FirefoxのUDPトラフィックが増加傾向
  • 従来利用していた NSPR は古く、提供API(PR_SendTo, PR_RecvFrom)は限定的
  • 現代OS ではsendmmsg/recvmmsgやGSO/GROなど 高効率API が利用可能
  • 本プロジェクトの目的
    • 最新システムコールによる UDP I/O高速化
    • メモリ安全 なRustによる実装でセキュリティ向上
    • 既存の QUIC実装(Rust) との統合性確保
  • quinn-udp (Rust製QUIC実装QuinnのUDP I/Oライブラリ)をベースに開発効率化
  • 2025年には 大多数のFirefoxユーザーへ展開、最大4倍のスループット向上を確認

UDP I/Oの基本と最適化手法

  • 従来方式
    • 1パケットずつsendto/recvfromで送受信
    • ユーザ空間→カーネル空間の往復コストが高い
  • バッチ送受信
    • sendmmsg/recvmmsg等により 複数パケットを一括処理
    • オーバーヘッド削減による効率化
  • セグメンテーションオフロード
    • GSO/GRO等により 大きなパケットをカーネル/ハードウェアで分割・結合
    • アプリ側の処理負荷軽減、NICの性能活用

NSPRからquinn-udpへの置き換え

  • 初期段階
    • まずは1パケットずつの送受信で quinn-udp を導入
    • Mozilla QUICクライアント・サーバのテスト実装を更新
  • バッチ処理対応
    • QUIC実装内のUDP処理パイプラインを バッチ処理型 へ改修
    • マルチメッセージAPIセグメンテーションオフロード の両方に対応
    • I/O高速化 やインプレース暗号化処理も追加

各プラットフォームの詳細と課題

Windows

  • WSASendMsg/WSARecvMsg で1パケット送受信または大きなセグメント送受信が可能
  • USO/URO (LinuxのGSO/GRO相当)対応を試みるも、互換性・安定性問題が発生
    • ARM64/WSL環境で URO利用時のバグ (セグメントサイズ未取得)によりページロード失敗
    • USO利用時に パケットロス増加ドライバクラッシュ報告 もあり
    • 現状、 URO/USOは無効化 し安定運用を優先

MacOS

  • sendmsg/recvmsg への置き換えは問題なし
  • UDPセグメンテーションオフロード非対応
  • sendmsg_x/recvmsg_x (非公開API)によるバッチ送受信も検討
    • 仕様不明・将来の削除リスクから 正式リリース見送り

Linux

  • sendmmsg/recvmmsg によるバッチ送受信と GSO/GRO によるセグメンテーションオフロードに完全対応
  • quinn-udp はGSOを優先採用、Firefoxでも同様方針
  • 1コネクション1ソケット 運用でプライバシー強化
    • GSO/GROの4タプル制限は問題にならず
  • ネットワークサンドボックスGSOサポート判定 など細かな対応も実施

Android

  • AndroidはLinuxとは別物、特に古いAPIやx86(32bit)サポートで苦労
    • x86では socketcall経由 でシステムコールする必要があり、セキュリティフィルタとの兼ね合いで問題発生
    • APIレベル25以下 ではECNビット付きsendmsgでエラー発生、リトライで回避
  • quinn-udpの改善恩恵 をFirefoxも享受

今後と知見の共有

  • quinn-udp を利用する他プロジェクトにも有用な知見
  • OSごとのI/O最適化ノウハウ やバグ回避策の蓄積
  • パフォーマンス計測 やデバッグ手法(例:WiresharkのGSO未対応問題)
  • 今後の課題
    • WindowsのUSO/URO安定化
    • MacOSの公式API対応状況
    • Androidのさらなる互換性強化

このプロジェクトは、 FirefoxのUDP I/O性能とセキュリティを大幅に向上 させたのみならず、 マルチプラットフォーム対応の難しさや最適化の知見 を広く提供するものです。他のUDP高速化を目指すプロジェクトにも役立つ情報源となります。

Hackerたちの意見

記者と何時間もやり取りした結果、運良くMozillaの社員でもあったので、問題を再現するために同じ色のノートパソコンを買っちゃった。ネットワーキングで問題を再現しようとするのは、やっぱり狂気だね。https://xkcd.com/2259/みたいに。

それに関しては、「地図ダウンロードの苦闘、パート2(技術)」のセクションが面白いよ。https://www.factorio.com/blog/post/fff-176(ドキュメントの最後の方)。

謎のパケットランツに関わったことがあるなら、これは面白いよ。ほとんどのネットワーク機器は、まだうまく処理できてないから。UDP/QUICは、ピークトラフィックを吸収できるほど大きなクラウドデプロイメントに基づいてないシステムをDoSできる。馬鹿げてるけど、クライアントトラフィックとの不均衡な帯域幅を達成できないホスティング運営を追い出すんだ。つまり、FAANGには問題ないけど、他の中小企業には致命的だね。だから、多くのLANはまだほとんどのUDPトラフィックを落として、通常のトラフィックに必要な部分だけをレート制限してる。良い一日を!=3

何かがRustで書かれてるってどうやって分かる?見出しに書いてあるよ!

そうだね。Rustは、良くも悪くも、IOパフォーマンスに特別な利点はないよ。

同意。これ、Cでも他の言語でもできたはずだよね。

それを除けば、HNのフロントページに載るRustプロジェクトの大半は、PopOSやRedox、昨日のWild linkerの話みたいに、そういうのじゃないからね。

確かにそうだけど、これはFirefoxのプロジェクトだから、Rustがこの種のコードをFirefoxで(再)書くために何年もかけて開発されたっていうのは関係あるよね。

sendmmsg/recvmmsgが「モダン」とされてるのはクレイジーだよね…だって、かなり前からあるのに。記事のLinuxセクションでio_uringが言及されると思ってたのに。

io_uringには本当に同等のものはないんだ。複数のUDPダイアグラムをバッチ処理することはできなくて、できるのは複数のsendmsgsとrecvmsgsをバッチ処理することだけ。GSO/GROがベストだね。sendmmsg/recvmmsgは確かに古いもので、いくつかのカーネル開発者はそれを廃止したいと思ってるみたい(笑)。

重要なポイントは真ん中に隠れてるね。>「極端なケースでは、純粋にCPUに依存するベンチマークで、スループットが400%も増加してるのが見られます。これはUDPネットワークアクティビティのCPU使用率が比例して減少することを意味します。」これはかなりすごいよね。特にポータブルクライアント(モバイルやノートパソコン)での電力効率が良くなるのは嬉しい。こういうプレゼンテーションは新鮮だね。最近は「モダン」スタックへの移行が良いことだとされて、データが伴わないことが多いから。

ユーザープログラムとシステムプログラムのために、ハードウェアアクセラレーションされたクロスコンテキストメッセージパッシングが見られる日が来るのかな。

面白い!WindowsやMacOSにGSO/GROの同等のものがあるとは知らなかったけど、バグがあるみたいで残念だね。

すごい!これはFirefoxがHTTP/3スタックのために自己署名証明書を再度有効にできるってこと?カスタム実装を使ってて、他の大きなQUICライブラリやデフォルトのビルドフラグを使ってないから?それは人々にとって大きな勝利だね。特に一般的なLANの使用ケースには。企業の使用ケースが「セキュリティ」の理由でそれを望まなくても。

Mozilla/FirefoxがChromeを使ってると文句言う人たちが、Rust製のものを支持する人たちに勝つかどうか楽しみだね。

彼らの改善は本物で必要なものだけど、4 Gb/sは速くないよ。たったの500 MB/sだからね。どこかで、たぶんコードにはないけど、すごく遅い部分があると思う。説明するよ。著者が引用したように、カーネルのコンテキストスイッチは1μs程度(システムコールにしては高すぎる気もするけど)だよ。平均して~500バイト/パケットでsendmsg()を呼んでも、500 MB/sには到達できる。これは標準の1500バイトMTUの約1/3だからね。MTUサイズのパケットを平均すると、4 Gb/sに到達するには2μsの処理時間に加えてフルシステムコールが必要になる。古い1 Gb/sの数値は、平均して~125バイト/パケット、MTUの約1/12か、約11μsの処理で達成できた。「でもネットワークスタックにはメモリコピーもあるよ。」簡単な3命令のメモリコピーは約10-20 GB/s、80-160 Gb/sで行けるよ。2μsで20-40 KBのコピーができる。UDPパケットをパケットに入れるために、ネットワークスタックが40-80(!)回もコピーしてるって言ってるの?商業用のネットワークドライバーを書いたことがあるけど、ゼロコピーなしでも、直接アクセスすれば基本的にメモリコピーの速度でUDPパケットをNICバッファに入れられるよ。「でも暗号化は遅い。」そんなに遅くないよ。これが5年以上前に行われたAES-128 GCMのパフォーマンスだよ。[1] Intel i5-6500は8年前のミッドレンジプロセッサで、平均1729 MB/sだ。500バイトのパケットの暗号化は300nsでできる、残りの2μsの予算の1/6だよ。現代のプロセッサはコアあたり3-5 GB/s、つまり約25-40 Gb/s、記載されたUDPスループットの6-10倍に近いみたい。[1] https://calomel.org/aesni_ssl_performance.html

「ゼロから始めるのではなく、QuinnプロジェクトのUDP I/Oライブラリであるquinn-udpの上に構築しました。これにより、開発がかなりスピードアップしました。Quinnプロジェクトには大感謝です。」 すごいね、じゃあ彼らをスポンサーしたってこと?