---
title: UDP と QUIC 転送層
url: https://doc.liz6.com/ja/networking/03-l4-transport-layer/02-udp-and-quic-transport-layer
locale: ja
area: networking
tags:
- networking
- l4-transport-layer
date: 2026-06-30
modified: 2026-07-16
description: 'UDP は単なる「ポート番号付きの IP」に過ぎませんが、QUIC はその上で UDP を用いた完全な信頼性のある転送をカプセル化します: 0-RTT ハンドシェイク、ストリーム多重化によるヘッド・オブ・ライン・ブロッキングの回避、接続の移行——TCP が 30 年かけて学んだことを、QUIC は一度で正しく実装しました。'
---

# 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/ポートは完全に可変です。

接続の移行:

<svg viewBox="0 0 720 390" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="QUIC 接続移行プロセス: Connection ID は不変、IP が変わっても接続は維持">
  <defs>
    <marker id="cma" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker>
  </defs>
  <rect width="720" height="390" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">QUIC 接続移行: Connection ID は不変、IP が変わっても接続は維持</text>
  <rect x="150" y="48" width="420" height="56" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="360" y="69" text-anchor="middle" font-size="13" font-weight="700" fill="#3730a3">クライアント · WiFi</text>
  <text x="360" y="88" text-anchor="middle" font-size="11" fill="#4f46e5">IP 192.168.1.x · QUIC フローは CID_A → CID_B で接続を識別</text>
  <line x1="360" y1="104" x2="360" y2="142" stroke="#475569" stroke-width="1.8" marker-end="url(#cma)"/>
  <text x="380" y="127" font-size="11" font-weight="700" fill="#334155">4G に切り替え (ネットワーク切替)</text>
  <rect x="150" y="142" width="420" height="56" rx="8" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="360" y="163" text-anchor="middle" font-size="13" font-weight="700" fill="#115e59">クライアント · 4G</text>
  <text x="360" y="182" text-anchor="middle" font-size="11" fill="#0f766e">IP 10.10.10.x · PATH_CHALLENGE を送信 (新しい IP、同じ CID_A → CID_B)</text>
  <line x1="360" y1="198" x2="360" y2="236" stroke="#475569" stroke-width="1.8" marker-end="url(#cma)"/>
  <text x="380" y="221" font-size="11" font-weight="700" fill="#334155">サーバー応答</text>
  <rect x="150" y="236" width="420" height="56" rx="8" fill="#e0e7ff" stroke="#c7d2fe"/>
  <text x="360" y="257" text-anchor="middle" font-size="13" font-weight="700" fill="#3730a3">PATH_RESPONSE</text>
  <text x="360" y="276" text-anchor="middle" font-size="11" fill="#4338ca">新しいパスの到達可能性を検証 → 移行確認</text>
  <line x1="360" y1="292" x2="360" y2="310" stroke="#475569" stroke-width="1.8" marker-end="url(#cma)"/>
  <rect x="60" y="314" width="600" height="56" rx="8" fill="#dcfce7" stroke="#4ade80"/>
  <text x="76" y="336" font-size="12.5" fill="#166534">接続はシームレスに移行! アプリケーション層は何の中断も認識しません——</text>
  <text x="76" y="356" font-size="12.5" fill="#166534">QUIC 接続は Connection ID (IP/ポートではなく) で識別され、ネットワーク変更でも接続は維持されます。</text>
</svg>

### ストリーム多重化

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*
