このページの目次
UDP と QUIC 転送層
UDP は単なる「ポート番号付きの IP」に過ぎませんが、QUIC はその上で UDP を用いた完全な信頼性のある転送をカプセル化します: 0-RTT ハンドシェイク、ストリーム多重化によるヘッド・オブ・ライン・ブロッキングの回避、接続の移行——TCP が 30 年かけて学んだことを、QUIC は一度で正しく実装しました。
概要
UDP は最もシンプルな転送層プロトコルです——8 バイトのヘッダ、コネクションレス、再送なし、フロー制御なし。その「簡素さ」こそが利点です: DNS クエリ(1 ラウンドトリップ)、リアルタイム音声・映像(フレームの欠落が体験に影響しない)、VPN(WireGuard)などは UDP を選択します。QUIC は UDP 上に完全な現代の転送層を実装し、TCP のフロー制御/輻輳制御と TLS 1.3 の暗号化を単一のプロトコルに統合し、TCP のヘッド・オブ・ライン・ブロッキングと接続の移行の問題を解決しました。HTTP/3 は QUIC 上で動作します。
UDP
UDP ヘッダ (固定 8 バイト):
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ソースポート | デスティネーションポート |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 長さ | チェックサム |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
長さ: UDP ヘッダ + ペイロードの合計長 (最小 8 = ヘッダのみ, 最大 65535)
チェックサム: IPv4 ではオプション (0=チェックサムなし), IPv6 では必須 — 擬似ヘッダ (src/dst IP +
プロトコル + 長さ) + UDP ヘッダ + ペイロードをカバー
UDP は「ポート + 長さ + オプションのチェックサム」以外は何もしません——コネクションレス、再送なし、フロー制御なし、輻輳制御なし。アプリケーション層で信頼性が必要な場合は、自前で実装する必要があります。QUIC はまさに「アプリケーション層が転送層を自前で実装する」ケースです。
QUIC 転送層
QUIC は「UDP + 信頼性包装」ではありません——UDP 上に実装された全く新しい転送層であり、TCP のフロー制御/輻輳制御と TLS の暗号化認証を 1 つのプロトコルに統合しています。
QUIC パケット構造
QUIC ロングヘッダ (Initial/Handshake/0-RTT パケット):
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1| タイプ | バージョン (32 ビット) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| DCID 長さ | デスティネーション接続 ID (0-20 バイト) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SCID 長さ | ソース接続 ID (0-20 バイト) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
QUIC ショートヘッダ (1-RTT データパケット):
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|1|S|R|R|R| デスティネーション接続 ID (...) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
ヘッダ形式 (ビット 0): 1=ロング, 0=ショート
固定ビット (ビット 1): 常に 1 — バージョンネゴシエーションの検出用 (ミドルボックスはこのビットを設定できない)
スピンビット (ショートヘッダのビット 2): 各 RTT で 1 回反転 — 観測者が RTT を測定可能
予約ビット: 0, ミドルボックスがこれらのビットを変更 → GREASE により検出 → 接続クローズ
QUIC 接続: 接続 ID
TCP 接続 = 5 タプル (src_ip, dst_ip, src_port, dst_port)。IP が変わると → 接続が切断されます。
QUIC 接続 = デスティネーション接続 ID + ソース接続 ID。エンドポイントはいつでも接続 ID を変更できます (NEW_CONNECTION_ID フレーム経由) → IP/ポートは完全に可変です。
接続の移行:
ストリーム多重化
1 つの QUIC 接続には複数のストリームがあります:
ストリーム ID (62 ビット、可変長エンコーディング):
ビット 0: 0=クライアント開始, 1=サーバー開始
ビット 1: 0=双方向, 1=単方向
ビット 2-61: ストリーム番号 (0, 1, 4, 8, 12, ... — クライアントは 0 から開始、サーバーは 1 から開始、
双方向のみ: 偶数がクライアント、奇数がサーバー)
各ストリームは独立:
- 独立したフロー制御 (MAX_STREAM_DATA / STREAM_DATA_BLOCKED) を持つ
- ストリーム 1 でパケットロス → ストリーム 1 のデータのみがブロックされ、ストリーム 3 は継続して配信
- TCP のヘッド・オブ・ライン・ブロッキングを解決: TCP ではすべてのストリームのバイトが同じバイトストリーム内にあるため、1 つのロスですべてがブロックされる
接続レベルのフロー制御: MAX_DATA / DATA_BLOCKED — すべてのストリームの合計受信量を制限
0-RTT
初回接続:
1-RTT: ClientHello → ServerHello → EncryptedExtensions → Finished → (データ)
以降の接続 (PSK モード):
クライアント: 最後のセッションチケット (PSK + 転送パラメータ + サーバーアドレスを含む) をキャッシュ
0-RTT: ClientHello + PSK + early data — ServerHello の受信前にデータ送信を開始
サーバー: PSK を検証 → early data を受け入れ → 直接処理
0-RTT のセキュリティリスク — リプレイ:
攻撃者が 0-RTT パケットをリプレイ → サーバーが重複処理 → 重複した副作用 (例: 2 回の課金)
QUIC の緩和策:
- アプリケーション層: 0-RTT では冪等操作 (GET, HEAD, OPTIONS) のみを許可
- 転送層: サーバーは REJECTED フレームを送信可能 → クライアントは 0-RTT データを再送信する必要あり
- AEAD リミット: 同じキーで多数の 0-RTT パケットを使用することはできない → リプレイウィンドウを制限
QUIC 損失検出
QUIC の損失検出は TCP よりも正確です:
TCP: 重複 ACK 3 回 → 再送 (ただし SACK では、再順序化と損失を区別)
QUIC:
- 各パケットに独立したパケット番号 (単調増加、再送パケットでも新しい番号を使用)
- 受信側 ACK フレーム: 最大受信番号 + 欠落範囲 → どのパケットが損失したかを正確に把握
- 損失検出:
1. 時間閾値: kTimeThreshold (9/8) × 最新の RTT を超える → 損失
2. パケット閾値: kPacketThreshold (3) 以上のより大きなパケットが ACK された → 損失
QUIC の応用
HTTP/3: QUIC の最大のユーザー (RFC 9114)
DNS/DoQ: DNS over QUIC (RFC 9250) — 0-RTT クエリ
SMB over QUIC: Windows Server 2022
RDP over QUIC: リモートデスクトップの耐パケットロス
参考
- RFC: 768, 8999-9002, 9000 付録 A (設計思想), 9250
- 実装: quiche (Cloudflare), lsquic (LiteSpeed), quinn (Rust), ngtcp2 (C)
- LWN: "QUIC and HTTP/3"
キーワード: UDP, QUIC, 接続の移行, 0-RTT, ストリーム多重化, ヘッド・オブ・ライン・ブロッキング, スピンビット, GREASE