【テクニカル・上級編】HTTP/2におけるDATAフレームの構造とペイロードの断片化 – HTTPプロトコル・通信規格実践ガイド

HTTP/2の心臓部:DATAフレームと「断片化」の美学

Webエンジニアやインフラアーキテクトが日々のトラフィック分析で見落としがちな、しかしパフォーマンスを決定づける極めて重要なレイヤーがある。それが、TCPのバイトストリームという原始的な世界の上に構築された、HTTP/2のフレーム構造、そしてアプリケーションデータを運ぶ「DATAフレーム」の挙動だ。

HTTP/1.1の「テキストベースで直列化された世界」から、HTTP/2の「バイナリフレーミングによる多重化の世界」へ移行したことで、私たちはヘッド・オブ・ライン・ブロック(HoLブロック)の呪縛から解放された。しかし、その裏側では、単一のTCPコネクション上で数千ものストリームが複雑に入り交じり、厳密なフロー制御とフレーム分割のアルゴリズムが火花を散らしている。

今回は、このHTTP/2アーキテクチャの根幹をなすDATAフレームのバイナリ構造と、プロトコル仕様の限界値である `SETTINGS_MAX_FRAME_SIZE` によるペイロードの断片化制御について、パケットキャプチャの向こう側に見えるカーネルの挙動まで含めて徹底的に解剖しよう。

—

1. パケットレベルで見るDATAフレームのバイナリ構造

HTTP/2のすべての通信は「フレーム」という単位でカプセル化される。TLSハンドシェイクとALPN(Application-Layer Protocol Negotiation)によるネゴシエーションが完了し、`PRI HTTP/2.0\r\n\r\nSM\r\n\r\n` というマジックプレフィックスが流れた瞬間から、ネットワーク上を流れるデータはすべて以下の9オクテット(バイト)の共通ヘッダーを持つバイナリの塊へと変貌する。

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length (24) |
+—————+—————+—————+
| Type (8) | Flags (8) |
+-+-+-+-+-+-+-+-+—————+——————————-+
|R| Stream Identifier (31) |
+-+—————–+——————————————-+
| Payload (Arbitrary Length)…
+—————————————————————+

9オクテットのヘッダーが語るもの

1. Length (24ビット): ペイロードのサイズを示す。最大で $2^{24}-1$ バイト、すなわち約16MB(16,777,215バイト)まで表現可能だが、デフォルトでは後述する `SETTINGS_MAX_FRAME_SIZE`(初期値 16,384バイト / 16KB)によって厳しく制限される。
2. Type (8ビット): フレームの種類。DATAフレームの場合は `0x0` がセットされる。
3. Flags (8ビット): フレーム固有のフラグ。DATAフレームにおいて最も重要なのは `END_STREAM (0x1)` だ。これが立つと、このストリームにおける送信側のデータ送信が完了した(半終了状態)ことを意味する。また、パディングを使用する場合は `PADDED (0x8)` が立ち、ペイロードの前にパディング長やパディングデータが付加される。
4. Stream Identifier (31ビット): このフレームがどの仮想ストリームに属するのかを示すID。クライアントが開始するストリームは奇数、サーバー側は偶数を使用する。最上位の1ビット(R)は予約領域であり常に0である。

そして、このヘッダーの直後に続くのが DATAペイロード である。これは、HTTP/1.1であればそのままプレーンテキストやチャンクとして流れていたはずのHTML、JSON、あるいは画像バイナリそのものだ。

—

2. なぜ分割するのか? `SETTINGS_MAX_FRAME_SIZE` の実務的意義

「1つの大きなファイルを送るなら、デカいフレームで一気に送ったほうがオーバーヘッドが少ないのでは?」
インフラエンジニアなら誰もが一度は抱く疑問だ。しかし、HTTP/2におけるフレーム分割(断片化)は、単なる気まぐれではなく、マルチプレクシングの公平性を保つための生命線である。

協調的マルチプレクシングの崩壊を防ぐ

HTTP/2の最大の武器は、1本のTCPコネクション上で複数のリクエストとレスポンスを同時に処理する「マルチプレクシング」にある。ここで、もしある重いAPIレスポンス(例えば5MBのJSON)が、分割されずに1つの巨大なDATAフレームとして送信されたとしよう。

もしセンドバッファやトランスポート層でその巨大フレームが占有されてしまうと、同じコネクション上で優先度の高い別のストリーム(例えば、画面描画に必須のCSSや緊急のプッシュ通知)が、その巨大なデータ送信が終わるまで一切処理を進められなくなる。結果として、HTTP/1.1のパイプライン処理時代に悩まされた「Head-of-Line Blocking」が、アプリケーションレイヤーで再発してしまうのだ。

これを防ぐため、HTTP/2では `SETTINGS_MAX_FRAME_SIZE` というパラメータが用意されている。

[サーバー] ──(フレーム1: 16KB)──> [TCP/TLS層] ──> [ネットワーク]
[サーバー] ──(フレーム2: 16KB)──> [TCP/TLS層] ──> [ネットワーク]
[サーバー] ──(フレーム3: 16KB)──> [TCP/TLS層] ──> [ネットワーク]
※途中に別のストリームのフレームを挟み込む(インターリーブ)ことが可能に

パラメータの仕様とネゴシエーション

  • 下限: 16,384バイト (16 KB) — すべてのHTTP/2実装が必ずサポートしなければならない最小値。
  • 上限: $2^{24}-1$ バイト (約 16 MB) — 仕様上の最大値。
  • 初期値: 16,384バイト (16 KB)

サーバーとクライアントは、初期接続確立後のSETTINGSフレームの交換において、`SETTINGS_MAX_FRAME_SIZE (0x5)` の値を通知し合う。ハイパフォーマンスなWebサーバー(Nginx, Envoy, Goの `net/http` など)では、この値をデフォルトの16KBから、ネットワーク環境やカーネルのバッファサイズに応じてチューニングすることが求められる場合がある。ただし、あまりに大きな値を設定しすぎると、前述のマルチプレクシングの恩恵が薄れるため、一般的には 16KBから最大でも65536バイト(64KB)程度 の範囲で運用されることがほとんどだ。

—

3. カーネル空間とユーザー空間の狭間で起きてること(Linuxチューニング)

DATAフレームの断片化と送信は、アプリケーション(Webサーバーなど)がシステムコール(`write` や `sendmsg`)を叩き、それがTLS層で暗号化され、最終的にLinuxカーネルのTCPスタックに渡されることで完結する。

ここで、インフラアーキテクトとして避けて通れないのが TCPウィンドウ制御とバッファチューニング だ。

HTTP/2フローコントロールとの二重構造

HTTP/2には、TCP自体のフローコントロールとは別に、ストリーム単位およびコネクション単位のフローコントロール(WINDOW_UPDATEフレーム) が存在する。
DATAフレームのペイロードがどれだけ細かく分割されていようとも、送信側は受信側から送られてくる `WINDOW_UPDATE` で許可されたウィンドウサイズ(Initial Window Sizeのデフォルトは65,535バイト)を超えてDATAフレームを送信することはできない。

もし受信側のアプリケーションの処理が追いつかずにウィンドウが枯渇すると、送信側はDATAフレームの送信を中断せざるを得なくなる。このとき、TCP層のバッファとHTTP/2のフローコントロールがどのように連動しているかを理解していないと、不可解なスループット低下(いわゆる「ゼロウィンドウ」問題やバッファーブロート)の解析で迷宮入りすることになる。

実践:Linuxカーネルパラメータの最適化

高スループットなHTTP/2ゲートウェイやリバースプロキシを構築する際、`/etc/sysctl.conf` に記述すべき推奨パラメータの例を以下に示す。これらは、細切れに生成されるDATAフレームを効率良くカーネルが処理し、TLSハンドシェイクと合わせてレイテンシを極限まで削るための知見である。

==============================================================================
Linuxカーネルネットワークチューニング (HTTP/2 高スループット・低レイテンシ向け)
==============================================================================

TCPソケットの送受信バッファの最小値、デフォルト値、最大値(バイト単位)
大量の並行ストリームが流れるHTTP/2では、TCPバッファを拡大しておくことが不可欠
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

TCPウィンドウのスケーリングを有効化(大帯域・高遅延ネットワーク対策)
net.ipv4.tcp_window_scaling = 1

TIME_WAITソケットの再利用を許可し、コネクション枯渇を防ぐ
net.ipv4.tcp_tw_reuse = 1

TCPパケットの遅延確認応答(Delayed ACK)の挙動最適化
小さなフレームが頻繁に往復する環境での無駄な待延を抑制
net.ipv4.tcp_low_latency = 1

キープアライブの調整(アイドル状態のHTTP/2コネクションの早期切断防止)
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 15
net.ipv4.tcp_keepalive_probes = 5

これらの設定により、アプリ層で刻まれたDATAフレームが、TCPセグメントへと効率よくマッピングされ、パケットロスのない滑らかなストリーミングを実現できる。

—

4. セキュリティの急所:HTTP/2に関連する脆弱性とDATAフレームの罠

極限のパフォーマンスを追求するプロトコルであるからこそ、セキュリティの専門家はDATAフレームの挙動に潜む魔物に警戒しなければならない。HTTP/2特有の脆弱性は、その多くがフレームのパース処理やリソース配分の不備に起因している。

1. HTTP/2 Continuation Flood (CVE-2023-45288 など)

厳密にはHEADERSフレームやCONTINUATIONフレームに関する脆弱性だが、ヘッダー圧縮(HPACK)とフレームの断片化の仕組みを悪用した攻撃だ。
攻撃者は、無限に続く小さなCONTINUATIONフレームを送りつけることで、サーバー側にヘッダーを保持し続けさせ、メモリを枯渇させる(DoS攻撃)。DATAフレーム自体はアプリケーションデータを運ぶが、プロトコル仕様における「フレームの断片化と結合」という思想の隙を突いた典型的な脅威である。

2. Rapid Reset Attack (CVE-2023-44487)

2023年後半に世界中のインフラエンジニアを震撼させた、記憶に新しい脆弱性。
HTTP/2の「ストリームのキャンセル(RST_STREAMフレーム)」の仕組みを悪用した攻撃。攻撃者は、リクエストを送信した直後に自ら `RST_STREAM` でそのストリームをキャンセルする。これを数千、数百万の規模で並行して行い、サーバー側に「リクエストの生成と破棄」の膨大な処理コストを強制する。
DATAフレームが実際に送信される前にストリームが破棄されるため、サーバーのCPUリソースのみが一方的に消費され、正当なユーザーのトラフィックが完全にブロックされる。

対策:

  • 使用しているWebサーバーやプロキシ(Nginx, Envoy, Apache, Node.jsなど)を速やかに最新バージョンにパッチ適用する。
  • Webサーバー側の設定で、許容する最大同時アクティブストリーム数や、一定時間あたりのキャンセル回数のしきい値を厳格に制限する。

NginxにおけるHTTP/2リソース制限のチューニング例
http {
# 1つのコネクション内で許可する最大同時ストリーム数
# デフォルトは無制限に近い値だが、攻撃耐性を考慮して適正値に絞る
http2_max_concurrent_streams 128;

# リクエストボディ(DATAフレームの塊)のバッファサイズ制限
client_body_buffer_size 16k;
client_max_body_size 10m;
}

—

5. まとめ:パケットの細部に宿る神を統御するために

HTTP/2におけるDATAフレームと、その断片化制御のメカニズムは、単なる「仕様書の1ページ」ではない。それは、限られたTCPの帯域を、いかに公平に、いかに美しく、数多のアプリケーションデータへ配分するかという、プロトコル設計者たちの卓越した思想の結晶である。

16KBというフレームサイズの裏側には、マルチプレクシングの公平性を守るための知恵があり、その下層ではLinuxカーネルのバッファやTLSの暗号化コンテキストがミリ秒単位で連動している。

インフラアーキテクトやテックリードとして、私たちが向き合うべきは単なるミドルウェアのバージョンアップだけではない。パケットキャプチャを開いたとき、そこに流れる9オクテットのヘッダーとDATAフレームの群れが、どのような意図で分割され、どのようなカーネルの息吹を経てユーザーに届いているのか――その「全レイヤーにわたる解像度」を持つことこそが、真に堅牢で爆速なWebインフラを構築するための唯一にして最強の武器となるのだ。

コメント

タイトルとURLをコピーしました