<p><!--META
{
"title": "TCPスライディングウィンドウ制御: RFC 793に基づく詳細解説",
"primary_category": "ネットワーク",
"secondary_categories": ["プロトコル","TCP/IP"],
"tags": ["TCP","RFC793","スライディングウィンドウ","フロー制御","ネットワークプロトコル"],
"summary": "RFC 793に定義されるTCPスライディングウィンドウ制御の仕組み、設計目標、パケット構造、セキュリティ、実装上の注意点を詳細に解説します。",
"mermaid": true,
"verify_level": "L0",
"tweet_hint": {"text":"RFC 793に基づくTCPスライディングウィンドウ制御の解説。フロー制御、パケット構造、セキュリティ、実装メモまで網羅。ネットワークエンジニア必見! #TCP #RFC793","hashtags":["#TCP","#RFC793"]},
"link_hints": ["https://datatracker.ietf.org/doc/html/rfc793"]
}
-->
本記事は<strong>Geminiの出力をプロンプト工学で整理した業務ドラフト(未検証)</strong>です。</p>
<h1 class="wp-block-heading">TCPスライディングウィンドウ制御: RFC 793に基づく詳細解説</h1>
<h2 class="wp-block-heading">背景</h2>
<p>信頼性の高いデータ転送は、現代のデジタル通信において不可欠です。トランスミッションコントロールプロトコル (TCP) は、インターネットプロトコルスイートの主要なコンポーネントとして、この信頼性を実現するための多くのメカニズムを提供します。その中核をなすのが「スライディングウィンドウ制御」であり、これは<strong>RFC 793</strong>(1981年9月発行)によってその基本的な仕組みが定義されました[1]。</p>
<p>TCPは、信頼性、順序保証、エラー訂正、およびフロー制御をエンドツーエンドで提供するコネクション指向のプロトコルです。アプリケーションがデータを送信する際、TCPはこれをセグメントに分割し、IPパケットとしてネットワークに送出します。このデータ転送プロセスにおいて、送信側が受信側の処理能力を超過してデータを送り続けることを防ぐために、フロー制御のメカニズムが必要となります。スライディングウィンドウ制御は、このフロー制御と、送信中の未確認データを効率的に管理するための基盤となります。</p>
<h2 class="wp-block-heading">設計目標</h2>
<p>RFC 793に記述されているTCPスライディングウィンドウ制御の主な設計目標は以下の通りです[1]:</p>
<ol class="wp-block-list">
<li><p><strong>信頼性の確保</strong>: 送信されたデータが確実に受信側に届くことを保証します。</p></li>
<li><p><strong>順序正しい配信</strong>: 受信側でデータが元の順序で再構築されることを保証します。</p></li>
<li><p><strong>フロー制御</strong>: 受信側のバッファオーバーフローを防ぎ、受信可能な速度でデータが送信されるように調整します。これは、受信側が処理できるよりも速くデータを送ってしまうと、パケットロスや再送が増加し、ネットワーク全体の効率が低下するため重要です。</p></li>
<li><p><strong>効率的なデータ転送</strong>: 各セグメントの確認応答を待つことなく、複数のセグメントを連続して送信できるようにすることで、ネットワークの帯域幅を最大限に活用します。これにより、待ち時間の長いネットワーク環境でも高いスループットを維持できます。</p></li>
<li><p><strong>ネットワーク資源の有効活用</strong>: 未確認のまま送信できるデータの量を制限することで、ネットワークの輻輳を悪化させずに、利用可能な帯域を適切に利用します。</p></li>
</ol>
<h2 class="wp-block-heading">詳細</h2>
<h3 class="wp-block-heading">スライディングウィンドウの概念</h3>
<p>TCPのスライディングウィンドウは、送信側と受信側がそれぞれ管理する論理的なバッファ領域と、その中のデータの状態を表すメカニズムです。これにより、送信側は、受信側が受信可能なデータ量(受信ウィンドウサイズ)の範囲内で、複数のセグメントを連続して送信できます。</p>
<h4 class="wp-block-heading">送信側のウィンドウ</h4>
<p>送信側は以下の3つの領域に分けられるウィンドウを管理します。</p>
<ul class="wp-block-list">
<li><p><strong>送信済み・確認済み領域</strong>: 既に送信され、受信側からACK(確認応答)を受け取ったデータ。この領域はウィンドウからスライドアウトします。</p></li>
<li><p><strong>送信済み・未確認領域</strong>: 送信はされたが、まだACKを受け取っていないデータ。この領域のデータは再送の対象となる可能性があります。この領域のサイズが、ACKを待つ間に送信できるデータの最大量を示します。</p></li>
<li><p><strong>未送信・送信許可領域</strong>: まだ送信されていないが、受信側のウィンドウ広告に基づいて送信が許可されているデータ。この領域のサイズが、現在の受信ウィンドウサイズによって制限されます。</p></li>
</ul>
<h4 class="wp-block-heading">受信側のウィンドウ</h4>
<p>受信側は以下の2つの領域に分けられるウィンドウを管理します。</p>
<ul class="wp-block-list">
<li><p><strong>受信済み・確認済み領域</strong>: 既に受信され、ACKが送信されたデータ。アプリケーションに渡された後、ウィンドウからスライドアウトします。</p></li>
<li><p><strong>未受信・受信許可領域</strong>: まだ受信されていないが、受信可能であると送信側に広告している領域。この領域のサイズが、送信側が送ってよいデータの最大量(受信ウィンドウサイズ)となります。</p></li>
</ul>
<p>送信側は、受信側から送られてくるACKに含まれる<code>Window Size</code>フィールドの情報をもとに、自身の「未送信・送信許可領域」のサイズを動的に調整します。これにより、受信側のバッファが溢れるのを防ぎます。</p>
<h3 class="wp-block-heading">TCPヘッダ構造と関連フィールド</h3>
<p>TCPヘッダは、スライディングウィンドウ制御に必要な情報を含んでいます。特に以下のフィールドが重要です[1]:</p>
<div class="codehilite">
<pre data-enlighter-language="generic">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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number | (32 bits)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number | (32 bits)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data Offset |Rsvd.|CWR|ECE|URG|ACK|PSH|RST|SYN|FIN| Window Size | (16 bits)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options (if any) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
</pre>
</div>
<ul class="wp-block-list">
<li><p><strong>Sequence Number (32 bits)</strong>: このセグメントのデータペイロードの最初のバイトのシーケンス番号。</p></li>
<li><p><strong>Acknowledgment Number (32 bits)</strong>: 送信者が次に期待するデータのシーケンス番号。これが累積的なACKです。</p></li>
<li><p><strong>Window Size (16 bits)</strong>: 受信側が現在受信可能なバイト数を示すフィールド。この値は、ACK Numberで示されるバイトから始まるデータに対して利用可能なバッファの量を示します。RFC 793では最大65,535バイトに制限されます。</p></li>
</ul>
<h3 class="wp-block-heading">ウィンドウの動作フロー</h3>
<h4 class="wp-block-heading">1. TCP 3ウェイハンドシェイクとウィンドウ広告</h4>
<p>接続確立時に、両端は互いの初期シーケンス番号とウィンドウサイズを交換します。</p>
<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">
sequenceDiagram
participant Client
participant Server
Client ->> Server: SYN (SEQ=100, WIN=64000)
Server ->> Client: SYN, ACK (SEQ=300, ACK=101, WIN=32000)
Client ->> Server: ACK (SEQ=101, ACK=301)
</pre></div>
<ul class="wp-block-list">
<li><p>Clientは最初のSYNでシーケンス番号100と自身の受信ウィンドウサイズ64000を広告。</p></li>
<li><p>ServerはSYN/ACKでシーケンス番号300、Clientの最初のSYNをACKする101、自身の受信ウィンドウサイズ32000を広告。</p></li>
<li><p>ClientはServerのSYNをACKする301を送信。</p></li>
</ul>
<h4 class="wp-block-heading">2. データ転送とウィンドウ更新</h4>
<p>データ送信後、受信側はデータを受信するたびにACKを返し、そのACK内に現在の利用可能な受信ウィンドウサイズを広告します。</p>
<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">
sequenceDiagram
participant Sender
participant Receiver
Sender ->> Receiver: Data A (SEQ=101, LEN=1000)
Sender ->> Receiver: Data B (SEQ=1101, LEN=1000)
Sender ->> Receiver: Data C (SEQ=2101, LEN=1000)
Note over Sender: ウィンドウ内で連続送信
Receiver ->> Sender: ACK (ACK=1101, WIN=62000)
Note over Receiver: Data Aを受信・処理、WINを更新
Receiver ->> Sender: ACK (ACK=2101, WIN=61000)
Note over Receiver: Data Bを受信・処理、WINを更新
Sender ->> Receiver: Data D (SEQ=3101, LEN=1000)
Note over Sender: ウィンドウがスライドし、新たなデータ送信
Receiver ->> Sender: ACK (ACK=3101, WIN=60000)
Note over Receiver: Data Cを受信・処理、WINを更新
Receiver ->> Sender: ACK (ACK=4101, WIN=59000)
Note over Receiver: Data Dを受信・処理、WINを更新
</pre></div>
<ul class="wp-block-list">
<li><p><code>Data A</code> (SEQ=101, LEN=1000) を送信後、Senderのウィンドウは (101-2100) が「送信済み・未確認」、(2101-…) が「未送信・送信許可」となる。</p></li>
<li><p><code>Receiver</code>が<code>Data A</code>を受信すると、次に期待する<code>ACK=1101</code>を返し、残りのバッファ量を示す<code>WIN=62000</code>を広告。</p></li>
<li><p><code>Sender</code>は<code>ACK=1101</code>を受け取ると、ウィンドウを<code>1000</code>バイト分スライドさせ、新たなデータを送信可能にする。</p></li>
</ul>
<h4 class="wp-block-heading">3. 再送制御</h4>
<p>ACKがタイムアウトした、または高速再送の条件が満たされた場合、送信側は未確認のデータを再送します。</p>
<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">
sequenceDiagram
participant Sender
participant Receiver
Sender ->> Receiver: Data 1 (SEQ=100, LEN=1000)
Sender ->> Receiver: Data 2 (SEQ=1100, LEN=1000)
Sender ->> Receiver: Data 3 (SEQ=2100, LEN=1000)
Note over Receiver: Data 2が途中でロスト
Sender ->> Receiver: Data 4 (SEQ=3100, LEN=1000)
Receiver ->> Sender: ACK (ACK=1100, WIN=...)
Note over Receiver: Data 1受信。Data 2は来ていないので、次に期待するのは1100
Receiver ->> Sender: ACK (ACK=1100, WIN=...)
Note over Receiver: Data 3受信。まだData 2が来ていないため、ACKは変化しない
Receiver ->> Sender: ACK (ACK=1100, WIN=...)
Note over Receiver: Data 4受信。3つの重複ACK (ACK=1100)
"Note over Sender: 3つの重複ACKを受信" -> "高速再送開始 (Fast Retransmit)"
Sender ->> Receiver: Data 2 (SEQ=1100, LEN=1000) [Retransmission]
Receiver ->> Sender: ACK (ACK=4100, WIN=...)
Note over Receiver: Data 2を受信し、Data 1, 2, 3, 4が全て揃ったので累積ACK
</pre></div>
<h4 class="wp-block-heading">4. ゼロウィンドウ</h4>
<p>受信側バッファが満杯になった場合、受信ウィンドウサイズを0と広告します。これにより送信側はデータの送信を一時停止します。</p>
<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">
sequenceDiagram
participant Sender
participant Receiver
Sender ->> Receiver: Data A (SEQ=100, LEN=1000)
Receiver ->> Sender: ACK (ACK=1100, WIN=0)
Note over Receiver: バッファが満杯
Note over Sender: WIN=0を受信。データ送信停止
Sender ->> Receiver: Zero Window Probe (ZWP)
Note over Sender: 一定時間後、WINの状態を確認するためZWPを送信
Receiver -->> Sender: ACK (ACK=1100, WIN=5000)
Note over Receiver: バッファに空きができた
Note over Sender: WIN=5000を受信。データ送信再開
Sender ->> Receiver: Data B (SEQ=1100, LEN=1000)
</pre></div>
<h3 class="wp-block-heading">ウィンドウの進化: ウィンドウ・スケール・オプション</h3>
<p>RFC 793で定義された16ビットの<code>Window Size</code>フィールドは、最大で65,535バイトのウィンドウサイズしか表現できません。しかし、高速なネットワーク(高帯域幅)と長距離(高遅延)の組み合わせ(高帯域幅遅延積、Bandwidth-Delay Product)では、このサイズはネットワークの能力を十分に活用するのに不十分です。</p>
<p>この制限を克服するため、<strong>RFC 1323</strong>(1992年10月発行)によって「TCPウィンドウ・スケール・オプション」が導入されました[2]。このオプションは、TCPハンドシェイク時に交換され、<code>Window Size</code>フィールドの値をシフトすることで、最大2^30バイト(約1ギガバイト)までのウィンドウサイズを可能にします。これにより、衛星通信のような高遅延ネットワークでも、大量のデータを効率的に転送できるようになりました。</p>
<h3 class="wp-block-heading">送信ウィンドウの状態遷移</h3>
<p>送信側が管理するスライディングウィンドウの状態を概念的に示すフローチャートです。</p>
<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">
flowchart LR
subgraph Sender's Window
A["送信済み & 確認済み"] -->|データ受信、ACK受信| B["送信済み & 未確認"]
B -->|ACKタイムアウトまたは高速再送| B
B -->|ACK受信| A
B -->|アプリケーションからデータ受信| C["未送信 & 送信許可"]
C -->|データ送信| B
A --o C
end
</pre></div>
<ul class="wp-block-list">
<li><p><code>A[送信済み & 確認済み]</code>: データは送信され、対応するACKが受信済み。この領域は「ウィンドウの外」にスライドし、再送の対象ではない。</p></li>
<li><p><code>B[送信済み & 未確認]</code>: データは送信されたが、まだACKを受信していない。再送タイマーが稼働しており、ACKタイムアウトや重複ACKにより再送される可能性がある。</p></li>
<li><p><code>C[未送信 & 送信許可]</code>: アプリケーションからデータが提供されたが、まだ送信されていない。受信側のウィンドウ広告によって送信が許可されている範囲。</p></li>
</ul>
<h2 class="wp-block-heading">相互運用性</h2>
<p>TCPのスライディングウィンドウ制御は、インターネットの基盤技術であり、多くのプロトコルやアプリケーションとの相互運用性を前提として設計されています。</p>
<ul class="wp-block-list">
<li><p><strong>上位層プロトコルとの連携</strong>: HTTP、FTP、SSH、SMTPなどのアプリケーション層プロトコルは、TCPが提供する信頼性の高いストリーム転送サービスを利用します。スライディングウィンドウは、これらのプロトコルが大量のデータを中断なく、かつ効率的に交換できるようにする基盤となります。</p></li>
<li><p><strong>下位層プロトコルとの連携</strong>: TCPセグメントはIPパケットにカプセル化され、物理ネットワークを介して転送されます。この際、IPレイヤーのMTU(Maximum Transmission Unit)とTCPのセグメントサイズ(MSS: Maximum Segment Size)は密接に関連します。TCPはPMTUD (Path MTU Discovery) を利用して、IPフラグメンテーションを回避し、最適なセグメントサイズを決定しようとします。</p></li>
<li><p><strong>フロー制御と輻輳制御の分離</strong>: RFC 793で定義されたスライディングウィンドウは、主に<strong>フロー制御</strong>(受信側のバッファ容量に応じた送信速度の調整)を担当します。これに対し、ネットワーク全体の混雑状況を考慮して送信速度を調整する<strong>輻輳制御</strong>は、RFC 2581(1999年4月発行)などの後続のRFCで定義され、TCPの機能として追加されました[3]。両者は異なる目的を持ちながら連携し、ネットワークの健全性を維持します。</p></li>
</ul>
<h2 class="wp-block-heading">セキュリティ考慮事項</h2>
<p>TCPスライディングウィンドウ制御は、その設計上、いくつかのセキュリティ上の考慮事項を伴います。</p>
<ul class="wp-block-list">
<li><p><strong>ウィンドウサイズ操作</strong>:</p>
<ul>
<li><p><strong>ゼロウィンドウ広告によるDoS</strong>: 悪意のある受信側が常にゼロウィンドウを広告することで、正当な送信側のデータ転送を停止させることができます。TCPのZero Window Probe (ZWP) はこれを緩和しますが、ZWP自体もリソースを消費します。</p></li>
<li><p><strong>非現実的に大きなウィンドウ広告</strong>: 悪意のある受信側が非常に大きなウィンドウサイズを広告し、実際にはそのバッファを持っていない場合、送信側は大量のデータを送信してしまい、パケットロスが多発したり、ネットワークに輻輳を引き起こしたりする可能性があります。</p></li>
</ul></li>
<li><p><strong>SYN Flood攻撃</strong>: TCP 3ウェイハンドシェイクを利用したDoS攻撃の一種です。攻撃者は大量のSYNパケットを送信しますが、最後のACKを返しません。これにより、サーバーは多数の半開き状態の接続(SYN_RECEIVED状態)を抱え込み、正規の接続を受け付けられなくなります。これは直接的にスライディングウィンドウの操作ではありませんが、TCPの接続確立プロセスと密接に関連し、サーバーのリソース(バッファなど)を枯渇させる可能性があります。</p></li>
<li><p><strong>シーケンス番号予測攻撃</strong>: 攻撃者がTCPシーケンス番号を正確に予測できる場合、既存のTCP接続に不正なパケットを挿入したり、接続をハイジャックしたりする可能性があります。スライディングウィンドウはシーケンス番号に依存するため、この攻撃はウィンドウの範囲内の番号をターゲットにすることがあります。現代のOSはランダムな初期シーケンス番号を使用することで、このリスクを大幅に低減しています。</p></li>
<li><p><strong>リソース枯渇</strong>: 大量のTCP接続や非常に大きなウィンドウサイズの設定は、システムのリソース(メモリバッファ、CPU)を枯渇させる可能性があります。適切なリソース管理と、不審な挙動を検出するメカニズムが必要です。</p></li>
</ul>
<h2 class="wp-block-heading">実装メモ</h2>
<p>TCPスライディングウィンドウを実装、または利用する上で考慮すべき点です。</p>
<ul class="wp-block-list">
<li><p><strong>MTU / Path MTU Discovery (PMTUD)</strong>:</p>
<ul>
<li><p>TCPのセグメントサイズ(MSS)は、IPレイヤーのMTUを超えないように設定されるべきです。IPフラグメンテーションは、中間ルーターの処理負荷を増やし、パケットロス時に全体の再送が必要になるなど、パフォーマンスに悪影響を与えます。</p></li>
<li><p><strong>RFC 1191</strong>(1990年11月発行)で定義されたPMTUDは、パス上の最小MTUを動的に発見し、それに基づいてMSSを調整することで、IPフラグメンテーションを回避します[4]。ファイアウォールなどでICMP <code>Fragmentation Needed</code>メッセージがブロックされると、PMTUDが正しく機能しない場合があります(PMTUDブラックホール)。</p></li>
</ul></li>
<li><p><strong>HOL blocking (Head-of-Line Blocking) 回避</strong>:</p>
<ul>
<li><p>TCPの受信側では、シーケンス番号が順不同で到着した場合、欠落しているセグメントを待つ間、それ以降の順序正しいセグメントもアプリケーションに渡されずにバッファに保持されます。これはTCPレベルのHOL Blockingではありません(TCPはバッファリングと並べ替えを行うため)。しかし、上位層のアプリケーションがTCPストリームから逐次的にしかデータを受け取れない場合、事実上のHOL Blockingが発生する可能性があります。</p></li>
<li><p>HTTP/2やHTTP/3 (QUIC) のような新しいプロトコルは、ストリーム多重化により、単一のトランスポート接続上で複数の論理ストリームを独立して処理することで、アプリケーションレベルのHOL Blockingを緩和しています。</p></li>
</ul></li>
<li><p><strong>キュー制御 (Queueing Control)</strong>:</p>
<ul>
<li><p>送信側と受信側の両方で、TCPバッファ(ソケットバッファ)の適切なサイズ設定が重要です。ウィンドウサイズはこれらのバッファによって制限されるため、不適切な設定はスループットの低下やメモリリソースの浪費につながります。</p></li>
<li><p>OSのネットワークスタックは、バッファサイズや再送キューの管理を効率的に行う必要があります。</p></li>
</ul></li>
<li><p><strong>優先度 (Priority)</strong>:</p>
<ul>
<li><p>TCP自体にパケットの優先度を設定するメカニズムはありませんが、基盤となるネットワークインフラ(ルーターやスイッチ)はDiffServ (Differentiated Services) やQoS (Quality of Service) を使用して、特定のTCPトラフィックに優先度を付けることができます。これは、リアルタイム通信(VoIP、ビデオ会議)など、レイテンシに敏感なアプリケーションで特に重要です。</p></li>
<li><p>アプリケーションレベルでの優先度付けは、複数のTCP接続を管理する際に、どのデータを先に送信するかを制御する際に考慮されることがあります。</p></li>
</ul></li>
</ul>
<h2 class="wp-block-heading">まとめ</h2>
<p>TCPスライディングウィンドウ制御は、<strong>RFC 793</strong>によってその基本が確立されて以来、信頼性、順序保証、フロー制御、および効率的なデータ転送を実現するTCPの中核的なメカニズムであり続けています。送信側と受信側が互いのバッファ容量を広告し、未確認データの範囲を動的に調整することで、ネットワークの利用効率を最大化しつつ、受信側のリソース枯渇を防ぎます。</p>
<p>16ビットのウィンドウサイズ制限は、後の<strong>RFC 1323</strong>で定義されたウィンドウ・スケール・オプションによって克服され、現代の高速・長距離ネットワークでもその能力を存分に発揮できるよう進化しました。この制御は、フロー制御と輻輳制御という異なる課題に対処する他のTCPメカニズムと連携しながら、現代のインターネット通信の信頼性を支える重要な要素です。実装においては、MTUやキュー制御、セキュリティ上の脅威に対する適切な対策が、安定したネットワーク運用には不可欠です。</p>
<hr/>
<p><strong>参考文献:</strong></p>
<p>[1] Postel, J. (September 1981). <em>Transmission Control Protocol DARPA Internet Program Protocol Specification</em>. IETF. <a href="https://datatracker.ietf.org/doc/html/rfc793">https://datatracker.ietf.org/doc/html/rfc793</a></p>
<p>[2] Jacobson, V., Braden, R., & Borman, D. (October 1992). <em>TCP Extensions for High Performance</em>. IETF. <a href="https://datatracker.ietf.org/doc/html/rfc1323">https://datatracker.ietf.org/doc/html/rfc1323</a></p>
<p>[3] Allman, M., Paxson, V., & Stevens, W. (April 1999). <em>TCP Congestion Control</em>. IETF. <a href="https://datatracker.ietf.org/doc/html/rfc2581">https://datatracker.ietf.org/doc/html/rfc2581</a></p>
<p>[4] Mogul, J., & Deering, S. (November 1990). <em>Path MTU Discovery</em>. IETF. <a href="https://datatracker.ietf.org/doc/html/rfc1191">https://datatracker.ietf.org/doc/html/rfc1191</a></p>
本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
TCPスライディングウィンドウ制御: RFC 793に基づく詳細解説
背景
信頼性の高いデータ転送は、現代のデジタル通信において不可欠です。トランスミッションコントロールプロトコル (TCP) は、インターネットプロトコルスイートの主要なコンポーネントとして、この信頼性を実現するための多くのメカニズムを提供します。その中核をなすのが「スライディングウィンドウ制御」であり、これはRFC 793(1981年9月発行)によってその基本的な仕組みが定義されました[1]。
TCPは、信頼性、順序保証、エラー訂正、およびフロー制御をエンドツーエンドで提供するコネクション指向のプロトコルです。アプリケーションがデータを送信する際、TCPはこれをセグメントに分割し、IPパケットとしてネットワークに送出します。このデータ転送プロセスにおいて、送信側が受信側の処理能力を超過してデータを送り続けることを防ぐために、フロー制御のメカニズムが必要となります。スライディングウィンドウ制御は、このフロー制御と、送信中の未確認データを効率的に管理するための基盤となります。
設計目標
RFC 793に記述されているTCPスライディングウィンドウ制御の主な設計目標は以下の通りです[1]:
信頼性の確保: 送信されたデータが確実に受信側に届くことを保証します。
順序正しい配信: 受信側でデータが元の順序で再構築されることを保証します。
フロー制御: 受信側のバッファオーバーフローを防ぎ、受信可能な速度でデータが送信されるように調整します。これは、受信側が処理できるよりも速くデータを送ってしまうと、パケットロスや再送が増加し、ネットワーク全体の効率が低下するため重要です。
効率的なデータ転送: 各セグメントの確認応答を待つことなく、複数のセグメントを連続して送信できるようにすることで、ネットワークの帯域幅を最大限に活用します。これにより、待ち時間の長いネットワーク環境でも高いスループットを維持できます。
ネットワーク資源の有効活用: 未確認のまま送信できるデータの量を制限することで、ネットワークの輻輳を悪化させずに、利用可能な帯域を適切に利用します。
詳細
スライディングウィンドウの概念
TCPのスライディングウィンドウは、送信側と受信側がそれぞれ管理する論理的なバッファ領域と、その中のデータの状態を表すメカニズムです。これにより、送信側は、受信側が受信可能なデータ量(受信ウィンドウサイズ)の範囲内で、複数のセグメントを連続して送信できます。
送信側のウィンドウ
送信側は以下の3つの領域に分けられるウィンドウを管理します。
送信済み・確認済み領域: 既に送信され、受信側からACK(確認応答)を受け取ったデータ。この領域はウィンドウからスライドアウトします。
送信済み・未確認領域: 送信はされたが、まだACKを受け取っていないデータ。この領域のデータは再送の対象となる可能性があります。この領域のサイズが、ACKを待つ間に送信できるデータの最大量を示します。
未送信・送信許可領域: まだ送信されていないが、受信側のウィンドウ広告に基づいて送信が許可されているデータ。この領域のサイズが、現在の受信ウィンドウサイズによって制限されます。
受信側のウィンドウ
受信側は以下の2つの領域に分けられるウィンドウを管理します。
送信側は、受信側から送られてくるACKに含まれるWindow Sizeフィールドの情報をもとに、自身の「未送信・送信許可領域」のサイズを動的に調整します。これにより、受信側のバッファが溢れるのを防ぎます。
TCPヘッダ構造と関連フィールド
TCPヘッダは、スライディングウィンドウ制御に必要な情報を含んでいます。特に以下のフィールドが重要です[1]:
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number | (32 bits)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number | (32 bits)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data Offset |Rsvd.|CWR|ECE|URG|ACK|PSH|RST|SYN|FIN| Window Size | (16 bits)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options (if any) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Sequence Number (32 bits): このセグメントのデータペイロードの最初のバイトのシーケンス番号。
Acknowledgment Number (32 bits): 送信者が次に期待するデータのシーケンス番号。これが累積的なACKです。
Window Size (16 bits): 受信側が現在受信可能なバイト数を示すフィールド。この値は、ACK Numberで示されるバイトから始まるデータに対して利用可能なバッファの量を示します。RFC 793では最大65,535バイトに制限されます。
ウィンドウの動作フロー
1. TCP 3ウェイハンドシェイクとウィンドウ広告
接続確立時に、両端は互いの初期シーケンス番号とウィンドウサイズを交換します。
sequenceDiagram
participant Client
participant Server
Client ->> Server: SYN (SEQ=100, WIN=64000)
Server ->> Client: SYN, ACK (SEQ=300, ACK=101, WIN=32000)
Client ->> Server: ACK (SEQ=101, ACK=301)
Clientは最初のSYNでシーケンス番号100と自身の受信ウィンドウサイズ64000を広告。
ServerはSYN/ACKでシーケンス番号300、Clientの最初のSYNをACKする101、自身の受信ウィンドウサイズ32000を広告。
ClientはServerのSYNをACKする301を送信。
2. データ転送とウィンドウ更新
データ送信後、受信側はデータを受信するたびにACKを返し、そのACK内に現在の利用可能な受信ウィンドウサイズを広告します。
sequenceDiagram
participant Sender
participant Receiver
Sender ->> Receiver: Data A (SEQ=101, LEN=1000)
Sender ->> Receiver: Data B (SEQ=1101, LEN=1000)
Sender ->> Receiver: Data C (SEQ=2101, LEN=1000)
Note over Sender: ウィンドウ内で連続送信
Receiver ->> Sender: ACK (ACK=1101, WIN=62000)
Note over Receiver: Data Aを受信・処理、WINを更新
Receiver ->> Sender: ACK (ACK=2101, WIN=61000)
Note over Receiver: Data Bを受信・処理、WINを更新
Sender ->> Receiver: Data D (SEQ=3101, LEN=1000)
Note over Sender: ウィンドウがスライドし、新たなデータ送信
Receiver ->> Sender: ACK (ACK=3101, WIN=60000)
Note over Receiver: Data Cを受信・処理、WINを更新
Receiver ->> Sender: ACK (ACK=4101, WIN=59000)
Note over Receiver: Data Dを受信・処理、WINを更新
Data A (SEQ=101, LEN=1000) を送信後、Senderのウィンドウは (101-2100) が「送信済み・未確認」、(2101-…) が「未送信・送信許可」となる。
ReceiverがData Aを受信すると、次に期待するACK=1101を返し、残りのバッファ量を示すWIN=62000を広告。
SenderはACK=1101を受け取ると、ウィンドウを1000バイト分スライドさせ、新たなデータを送信可能にする。
3. 再送制御
ACKがタイムアウトした、または高速再送の条件が満たされた場合、送信側は未確認のデータを再送します。
sequenceDiagram
participant Sender
participant Receiver
Sender ->> Receiver: Data 1 (SEQ=100, LEN=1000)
Sender ->> Receiver: Data 2 (SEQ=1100, LEN=1000)
Sender ->> Receiver: Data 3 (SEQ=2100, LEN=1000)
Note over Receiver: Data 2が途中でロスト
Sender ->> Receiver: Data 4 (SEQ=3100, LEN=1000)
Receiver ->> Sender: ACK (ACK=1100, WIN=...)
Note over Receiver: Data 1受信。Data 2は来ていないので、次に期待するのは1100
Receiver ->> Sender: ACK (ACK=1100, WIN=...)
Note over Receiver: Data 3受信。まだData 2が来ていないため、ACKは変化しない
Receiver ->> Sender: ACK (ACK=1100, WIN=...)
Note over Receiver: Data 4受信。3つの重複ACK (ACK=1100)
"Note over Sender: 3つの重複ACKを受信" -> "高速再送開始 (Fast Retransmit)"
Sender ->> Receiver: Data 2 (SEQ=1100, LEN=1000) [Retransmission]
Receiver ->> Sender: ACK (ACK=4100, WIN=...)
Note over Receiver: Data 2を受信し、Data 1, 2, 3, 4が全て揃ったので累積ACK
4. ゼロウィンドウ
受信側バッファが満杯になった場合、受信ウィンドウサイズを0と広告します。これにより送信側はデータの送信を一時停止します。
sequenceDiagram
participant Sender
participant Receiver
Sender ->> Receiver: Data A (SEQ=100, LEN=1000)
Receiver ->> Sender: ACK (ACK=1100, WIN=0)
Note over Receiver: バッファが満杯
Note over Sender: WIN=0を受信。データ送信停止
Sender ->> Receiver: Zero Window Probe (ZWP)
Note over Sender: 一定時間後、WINの状態を確認するためZWPを送信
Receiver -->> Sender: ACK (ACK=1100, WIN=5000)
Note over Receiver: バッファに空きができた
Note over Sender: WIN=5000を受信。データ送信再開
Sender ->> Receiver: Data B (SEQ=1100, LEN=1000)
ウィンドウの進化: ウィンドウ・スケール・オプション
RFC 793で定義された16ビットのWindow Sizeフィールドは、最大で65,535バイトのウィンドウサイズしか表現できません。しかし、高速なネットワーク(高帯域幅)と長距離(高遅延)の組み合わせ(高帯域幅遅延積、Bandwidth-Delay Product)では、このサイズはネットワークの能力を十分に活用するのに不十分です。
この制限を克服するため、RFC 1323(1992年10月発行)によって「TCPウィンドウ・スケール・オプション」が導入されました[2]。このオプションは、TCPハンドシェイク時に交換され、Window Sizeフィールドの値をシフトすることで、最大2^30バイト(約1ギガバイト)までのウィンドウサイズを可能にします。これにより、衛星通信のような高遅延ネットワークでも、大量のデータを効率的に転送できるようになりました。
送信ウィンドウの状態遷移
送信側が管理するスライディングウィンドウの状態を概念的に示すフローチャートです。
flowchart LR
subgraph Sender's Window
A["送信済み & 確認済み"] -->|データ受信、ACK受信| B["送信済み & 未確認"]
B -->|ACKタイムアウトまたは高速再送| B
B -->|ACK受信| A
B -->|アプリケーションからデータ受信| C["未送信 & 送信許可"]
C -->|データ送信| B
A --o C
end
A[送信済み & 確認済み]: データは送信され、対応するACKが受信済み。この領域は「ウィンドウの外」にスライドし、再送の対象ではない。
B[送信済み & 未確認]: データは送信されたが、まだACKを受信していない。再送タイマーが稼働しており、ACKタイムアウトや重複ACKにより再送される可能性がある。
C[未送信 & 送信許可]: アプリケーションからデータが提供されたが、まだ送信されていない。受信側のウィンドウ広告によって送信が許可されている範囲。
相互運用性
TCPのスライディングウィンドウ制御は、インターネットの基盤技術であり、多くのプロトコルやアプリケーションとの相互運用性を前提として設計されています。
上位層プロトコルとの連携: HTTP、FTP、SSH、SMTPなどのアプリケーション層プロトコルは、TCPが提供する信頼性の高いストリーム転送サービスを利用します。スライディングウィンドウは、これらのプロトコルが大量のデータを中断なく、かつ効率的に交換できるようにする基盤となります。
下位層プロトコルとの連携: TCPセグメントはIPパケットにカプセル化され、物理ネットワークを介して転送されます。この際、IPレイヤーのMTU(Maximum Transmission Unit)とTCPのセグメントサイズ(MSS: Maximum Segment Size)は密接に関連します。TCPはPMTUD (Path MTU Discovery) を利用して、IPフラグメンテーションを回避し、最適なセグメントサイズを決定しようとします。
フロー制御と輻輳制御の分離: RFC 793で定義されたスライディングウィンドウは、主にフロー制御(受信側のバッファ容量に応じた送信速度の調整)を担当します。これに対し、ネットワーク全体の混雑状況を考慮して送信速度を調整する輻輳制御は、RFC 2581(1999年4月発行)などの後続のRFCで定義され、TCPの機能として追加されました[3]。両者は異なる目的を持ちながら連携し、ネットワークの健全性を維持します。
セキュリティ考慮事項
TCPスライディングウィンドウ制御は、その設計上、いくつかのセキュリティ上の考慮事項を伴います。
ウィンドウサイズ操作:
ゼロウィンドウ広告によるDoS: 悪意のある受信側が常にゼロウィンドウを広告することで、正当な送信側のデータ転送を停止させることができます。TCPのZero Window Probe (ZWP) はこれを緩和しますが、ZWP自体もリソースを消費します。
非現実的に大きなウィンドウ広告: 悪意のある受信側が非常に大きなウィンドウサイズを広告し、実際にはそのバッファを持っていない場合、送信側は大量のデータを送信してしまい、パケットロスが多発したり、ネットワークに輻輳を引き起こしたりする可能性があります。
SYN Flood攻撃: TCP 3ウェイハンドシェイクを利用したDoS攻撃の一種です。攻撃者は大量のSYNパケットを送信しますが、最後のACKを返しません。これにより、サーバーは多数の半開き状態の接続(SYN_RECEIVED状態)を抱え込み、正規の接続を受け付けられなくなります。これは直接的にスライディングウィンドウの操作ではありませんが、TCPの接続確立プロセスと密接に関連し、サーバーのリソース(バッファなど)を枯渇させる可能性があります。
シーケンス番号予測攻撃: 攻撃者がTCPシーケンス番号を正確に予測できる場合、既存のTCP接続に不正なパケットを挿入したり、接続をハイジャックしたりする可能性があります。スライディングウィンドウはシーケンス番号に依存するため、この攻撃はウィンドウの範囲内の番号をターゲットにすることがあります。現代のOSはランダムな初期シーケンス番号を使用することで、このリスクを大幅に低減しています。
リソース枯渇: 大量のTCP接続や非常に大きなウィンドウサイズの設定は、システムのリソース(メモリバッファ、CPU)を枯渇させる可能性があります。適切なリソース管理と、不審な挙動を検出するメカニズムが必要です。
実装メモ
TCPスライディングウィンドウを実装、または利用する上で考慮すべき点です。
MTU / Path MTU Discovery (PMTUD):
TCPのセグメントサイズ(MSS)は、IPレイヤーのMTUを超えないように設定されるべきです。IPフラグメンテーションは、中間ルーターの処理負荷を増やし、パケットロス時に全体の再送が必要になるなど、パフォーマンスに悪影響を与えます。
RFC 1191(1990年11月発行)で定義されたPMTUDは、パス上の最小MTUを動的に発見し、それに基づいてMSSを調整することで、IPフラグメンテーションを回避します[4]。ファイアウォールなどでICMP Fragmentation Neededメッセージがブロックされると、PMTUDが正しく機能しない場合があります(PMTUDブラックホール)。
HOL blocking (Head-of-Line Blocking) 回避:
TCPの受信側では、シーケンス番号が順不同で到着した場合、欠落しているセグメントを待つ間、それ以降の順序正しいセグメントもアプリケーションに渡されずにバッファに保持されます。これはTCPレベルのHOL Blockingではありません(TCPはバッファリングと並べ替えを行うため)。しかし、上位層のアプリケーションがTCPストリームから逐次的にしかデータを受け取れない場合、事実上のHOL Blockingが発生する可能性があります。
HTTP/2やHTTP/3 (QUIC) のような新しいプロトコルは、ストリーム多重化により、単一のトランスポート接続上で複数の論理ストリームを独立して処理することで、アプリケーションレベルのHOL Blockingを緩和しています。
キュー制御 (Queueing Control):
優先度 (Priority):
TCP自体にパケットの優先度を設定するメカニズムはありませんが、基盤となるネットワークインフラ(ルーターやスイッチ)はDiffServ (Differentiated Services) やQoS (Quality of Service) を使用して、特定のTCPトラフィックに優先度を付けることができます。これは、リアルタイム通信(VoIP、ビデオ会議)など、レイテンシに敏感なアプリケーションで特に重要です。
アプリケーションレベルでの優先度付けは、複数のTCP接続を管理する際に、どのデータを先に送信するかを制御する際に考慮されることがあります。
まとめ
TCPスライディングウィンドウ制御は、RFC 793によってその基本が確立されて以来、信頼性、順序保証、フロー制御、および効率的なデータ転送を実現するTCPの中核的なメカニズムであり続けています。送信側と受信側が互いのバッファ容量を広告し、未確認データの範囲を動的に調整することで、ネットワークの利用効率を最大化しつつ、受信側のリソース枯渇を防ぎます。
16ビットのウィンドウサイズ制限は、後のRFC 1323で定義されたウィンドウ・スケール・オプションによって克服され、現代の高速・長距離ネットワークでもその能力を存分に発揮できるよう進化しました。この制御は、フロー制御と輻輳制御という異なる課題に対処する他のTCPメカニズムと連携しながら、現代のインターネット通信の信頼性を支える重要な要素です。実装においては、MTUやキュー制御、セキュリティ上の脅威に対する適切な対策が、安定したネットワーク運用には不可欠です。
参考文献:
[1] Postel, J. (September 1981). Transmission Control Protocol DARPA Internet Program Protocol Specification. IETF. https://datatracker.ietf.org/doc/html/rfc793
[2] Jacobson, V., Braden, R., & Borman, D. (October 1992). TCP Extensions for High Performance. IETF. https://datatracker.ietf.org/doc/html/rfc1323
[3] Allman, M., Paxson, V., & Stevens, W. (April 1999). TCP Congestion Control. IETF. https://datatracker.ietf.org/doc/html/rfc2581
[4] Mogul, J., & Deering, S. (November 1990). Path MTU Discovery. IETF. https://datatracker.ietf.org/doc/html/rfc1191
コメント