RFC 9293: トランスミッション・コントロール・プロトコル(TCP) ─ Part1
要旨
本文書は、トランスミッション・コントロール・プロトコル(TCP)を規定する。TCPは、インターネット・プロトコル・スタックにおける重要なトランスポート層プロトコルであり、数十年にわたるインターネットの利用と成長を通じて継続的に進化してきた。この間、RFC 793で規定されたTCPに多くの変更が加えられたが、これらは断片的にしか文書化されていない。本文書は、RFC 793のプロトコル仕様とともに、これらの変更を収集し、まとめたものである。本文書は、RFC 793及び、RFC 793の一部を更新したRFC 879、2873、6093、6429、6528、6691を廃止する。本文書は、RFC 1011及びRFC 1122を更新しており、これらの文書のTCPの要件を扱っている部分を置き換えるものとして考慮する必要がある。また、SYN-RECEIVED状態でのリセット処理に少し説明を追加することで、RFC 5961を更新している。RFC 793のTCPヘッダ制御ビットもRFC 3168に基づいて更新している。
本文書の位置付け
本文書は、インターネット標準化過程の文書である。
この文書はInternet Engineering Task Force (IETF)の成果物である。IETFコミュニティのコンセンサスを表すものである。公開レビューを受けており、Internet Engineering Steering Group (IESG)によって公開が承認されている。インターネット標準の詳細については、RFC 7841のセクション 2を参照のこと。
本文書の現在のステータス、正誤表、および文書に対するフィードバックの提供方法に関する情報は、<https://www.rfc-editor.org/info/rfc9293>で入手できる。
著作権表示
Copyright (c) 2024 IETFトラストおよび文書の著者として特定された人物。無断転載を禁じる。
本文書は、BCP 78および文書の発行日において有効なIETF文書に関するIETFトラストの法的規定(<https://trustee.ietf.org/license-info>) に従うものとする。これらの文書には、本文書に関するあなたの権利と制限が記載されているため、注意深く確認して欲しい。文書から抽出されたコード・コンポーネントには、トラスト法的条項のセクション4.eに記載されている改訂BSDライセンスのテキストを含まなければならず、改訂BSDライセンスに記載されているように保証なしで提供する。
本文書には、2008年11月10日以前に発行または一般公開されたIETF文書またはIETF寄稿の資料が含まれている場合がある。このような資料の著作権を管理する人物は、IETF標準化プロセス外での改変を許可する権利を、IETFトラストに付与していない可能性がある。このような資料の著作権を管理する人物から適切なライセンスを取得しない限り、この文書をIETF標準化プロセスの範囲外で可変してはならない。また、RFCとして発行するためにフォーマットしたり、英語以外の言語に翻訳する場合を除き、この文書の派生著作物をIETF標準化プロセスの範囲外で作成することはできない。
1. 目的と範囲
1981年に、RFC 793[16]が公開され、トランスミッション・コントロール・プロトコル(TCP)が文書化され、それ以前に公開されていたTCPの仕様を置き換えた。
それ以来、TCPは広く実装され、インターネット上の多数のアプリケーションのトランスポート・プロトコルとして使用されてきた。
数十年にわたり、RFC 793と他の多くの文書が組み合わされ、TCPの中核仕様として機能してきた[49]。時間の経過とともに、RFC 793に対して多くの正誤表が提出された。また、セキュリティ、パフォーマンス、その他多くの面で欠陥が発見され、解決してきた。多くの拡張は、多くの個別の文書にまたがっており、時間の経過とともに増えてきた。これらは、基本仕様の包括的な更新としてまとめられることはなかった。
本文書の目的は、基本的なTCP機能仕様(RFC 793)に対して行われたIETF標準化過程のすべての変更と解説をまとめ、仕様の更新版に統合することである。
TCPで使用される重要なアルゴリズム(例: 輻輳制御)については、いくつかの関連文書を参照しているが、本文書に完全には含まれていない。この基本仕様は、個別に開発され組み込まれる複数の追加アルゴリズムと併用できるため、これは意識的な選択である。本文書は、すべてのTCP実装が相互運用するためにサポートしなければならない共通の基本仕様に焦点を当てている。いくつかの追加のTCP機能の中には、それ自体が非常に複雑になっているものもあるため(例えば、高度な損失回復や輻輳制御)、将来の関連文書ではこれらを同様にまとめるかも知れない。
プログラムで実装されるTCPセグメント・フォーマット、生成、処理ルールを説明するプロトコル仕様に加え、RFC 793や他の更新には、読者がプロトコルの設計と運用の側面を理解するための有益で解説的な文章も含まれている。本文書は、この有益な文章を変更したり、更新したりするものではなく、規範となるプロトコル仕様の更新のみに焦点を当てている。本文書は、必要に応じて、重要な説明や理論的根拠を含む文書への参照を保持する。
本文書は、既存のTCP実装の適合性をチェックする際にも、新しい実装を開発する際にも役立つことを目的としている。
2. はじめに
RFC 793は、TCPの設計目標についての説明と、コネクションの確立、コネクションの終了、損失を修復するためのパケットの再送を含む、TCPの動作例を提供している。
本文書は、最新のTCP実装で想定される基本的な機能を説明し、RFC 793のプロトコル仕様を置き換えるものである。RFC 793のセクション1と2にある序論と理念の内容を複製したり、更新しようとするものではない。動作理論、理論的根拠、設計上の決定に関する詳細な議論を提供するため、他の文書を参照している。本文書は、プロトコルの規範的な動作にのみ焦点を当てている。
「TCPのロードマップ」[49]は、TCPを定義し、さまざまな重要なアルゴリズムを規定するRFCについて、より広範なガイドを提供する。TCPのロードマップには、本文書で規定している基本動作を超えて、TCPのパフォーマンスの向上など、強く推奨される拡張に関するセクションを含んでいる。一例として、輻輳制御の実装([8]など)はTCPの要件ではあるが、基本的な相互運用性に影響を与えない多くのオプションや可能性があるため、それ自体が複雑なテーマであり、本文書では詳しく説明していない。同様に、今日のほとんどのTCP実装には[47]の高性能拡張が含まれているが、これらは厳密には必須ではなく、本文書で説明するわけではない。TCPのマルチパスに関する考慮事項も、[59]で別途規定されている。
RFC 793からの変更点リストはセクション5に記載している。
2.1. 要件言語
この文書のキーワード「MUST」、「MUST NOT」、「REQUIRED」、「SHALL」、「SHALL NOT」、「SHOULD」、「SHOULD NOT」、「RECOMMENDED」、「NOT RECOMMENDED」、「MAY」、および「OPTIONAL」は、ここに示すように、すべて大文字で表示される場合に限り、 BCP 14 [3] [12]の記述に従って解釈される。
本文書におけるRFC 2119キーワードの使用は、それぞれ個別にラベル付けされ、実装要件をまとめた付録Bで参照される。
「MUST」を使用した文章には、「MUST-X」というラベルが付けられ、Xは付録Bから参照されたときにその要件を簡単に見つけられるようにするための数値識別子である。
同様に、「SHOULD」を使用した文章には「SHLD-X」、「MAY」には「MAY-X」、「RECOMMENDED」には「REC-X」というラベルが付けられる。
このラベル付けの目的上、「SHOULD NOT」と「MUST NOT」は、「SHOULD」と「MUST」のインスタンスと同じラベルが付けられる。
2.2. TCPの主な概念
TCPは、アプリケーションに対して、信頼性の高い、正しい順序で届くバイトストリーム・サービスを提供する。
アプリケーションのバイトストリームは、TCPセグメントを介してネットワーク上で伝達され、各TCPセグメントはインターネット・プロトコル(IP)データグラムとして送信される。
TCPの信頼性は、(シーケンス番号による)パケット損失と(セグメントごとのチェックサムによる)エラーの検出、及び再送による修正で構成される。
TCPはデータのユニキャスト配信をサポートしている。下位層の転送動作の変更に起因する不安定性のリスクはあるが[46]、修正なしでTCPをうまく利用できるエニーキャスト・アプリケーションがある。
TCPはコネクション指向だが、本質的に活性検出機能は含まれていない。
データフローは、TCPコネクション上で双方向にサポートされているが、アプリケーションは必要に応じて、一方向にのみデータを送信することもできる。
TCPはアプリケーション・サービスを識別し、ホスト間で異なるフローを多重化するために、ポート番号を使用する。
他のトランスポート・プロトコルと比較したTCP機能についてのより詳細な説明は、[52]のセクション3.1に記載されている。TCPの開発動機とインターネット・プロトコル・スタックにおけるTCPの役割の詳細については、[16]のセクション2及び以前のバージョンのTCP仕様に記載されている。
3. 機能仕様
3.1. ヘッダ・フォーマット
TCPセグメントはインターネット・データグラムとして送信される。インターネット・プロトコル(IP)ヘッダは、送信元と送信先のホストアドレスを含むいくつかの情報フィールドを持つ[1] [13]。TCPヘッダはIPヘッダの後に続き、TCP固有の情報を提供する。この分割によって、TCP以外のホストレベルのプロトコルの存在が可能になった。インターネット・プロトコル・スイート(群)の初期開発では、IPヘッダ・フィールドはTCPの一部だった。
本文書では、TCPヘッダを使用するTCPについて説明する。
TCPヘッダとそれに続くセグメント内のユーザデータは、[66]のスタイルを使用して、以下のようにフォーマットされる:
図1: TCPヘッダ・フォーマット
ここで、
-
送信元ポート: 16ビット
送信元ポート番号
-
送信先ポート: 16ビット
送信先ポート番号
-
シーケンス番号: 32ビット
このセグメントの最初のデータ・オクテットのシーケンス番号(SYNフラグがセットされている場合を除く)。SYNがセットされている場合、シーケンス番号は初期シーケンス番号(ISN)で、最初のデータ・オクテットはISN+1である。
-
確認応答番号: 32ビット
ACK制御ビットがセットされている場合、このフィールドにはセグメントの受信側が期待する次のシーケンス番号が含まれる。コネクションが確立されると、これは常に送信される。
-
データ・オフセット(DOffset): 4ビット
TCPヘッダ内の32ビットワード数。これはデータの開始位置を示す。TCPヘッダ (オプションを含めても)32ビットの整数倍の長さである。
-
予約済み(Rsrvd): 4ビット
将来の利用のために予約された制御ビット。対応する将来の機能が送信ホストまたは受信ホストが実装していない場合、生成されるセグメントはゼロでなければならず、受信側では無視しなければならない。
-
制御ビット:
制御ビットは「フラグ」とも呼ばれる。割り当ては、IANAが「TCP Header Flags」レジストリ[62]で管理している。現在割り当てられている制御ビットは、CWR、ECE、URG、ACK、PSH、RST、SYN、およびFINである。
-
CWR: 1ビット
輻輳ウィンドウが縮小した([6]を参照)。
-
ECE: 1ビット
ECN-Echo ([6]を参照)。
-
URG: 1ビット
緊急ポインタ・フィールドが有効である。
-
ACK: 1ビット
確認応答フィールドが有効である。
-
PSH: 1ビット
プッシュ機能(セクション3.9.1の送信コールの説明を参照)。
-
RST: 1ビット
コネクションをリセットする。
-
SYN: 1ビット
シーケンス番号を同期させる。
-
FIN: 1ビット
送信側のデータがない(送信終了)。
-
-
ウィンドウ・サイズ: 16ビット
このセグメントの送信者が受け入れる意思がある確認応答フィールドに示されているデータ・オクテット数。ウィンドウ・スケーリング拡張を使用すると、値がシフトされる[47]。
ウィンドウ・サイズは符号なしの数値として扱わなければならない、さもなければ大きなウィンドウ・サイズは負のウィンドウのように見え、TCPは機能しなくなる(MUST-1)。実装では、コネクション・レコードの送信及び受信ウィンドウ・サイズに32ビット・フィールドを確保し、すべてのウィンドウ・サイズの計算を32ビットで行うことを推奨する(REC-1)。
-
チェックサム: 16ビット
チェックサム・フィールドは、ヘッダとテキストに含まれる全16ビットワードの1の補数の総和の1の補数の下位16ビットである。チェックサムの計算は、計算されるデータの16ビットの整列を保証する必要がある。セグメントに奇数のヘッダとテキストが含まれる場合、最後のオクテットの右にゼロをパディングすることで、チェックサム用の16ビットワードを形成し、整列を行う。このパッドはセグメントの一部としては伝送されない。チェックサムの計算中に、チェックサム・フィールド自体はゼロに置き換えられる。
チェックサムは、概念的にはTCPヘッダの先頭に付加される擬似ヘッダ(図2)もカバーする。擬似ヘッダは、IPv4では96ビット、IPv6では320ビットである。擬似ヘッダにチェックサムを含めることで、TCPコネクションは誤ってルーティングされたセグメントから保護される。この情報はIPヘッダに格納され、IP層のTCP実装による呼び出しの引数または結果に含まれ、TCP/ネットワーク・インタフェースを介して転送される。
図2: IPv4擬似ヘッダーIPv4の疑似ヘッダ・コンポーネント:
- 送信元アドレス: ネットワーク・バイトオーダーのIPv4送信元アドレス
- 送信先アドレス: ネットワーク・バイトオーダーのIPv4送信先アドレス
- ゼロ: ビットはゼロに設定される
- プロトコル: IPヘッダのプロトコル番号
- TCPのパケット長: TCPヘッダ長とオクテット単位のデータ長を加えたもの(これは明示的に送信される量ではなく計算される)で、擬似ヘッダの12オクテットはカウントされない。
IPv6の場合、擬似ヘッダはRFC 8200[13]のセクション8.1で定義されており、IPv6送信元アドレスと送信先アドレス、上位層パケット長(IPv4擬似ヘッダのTCP長に相当する32ビット値)、3バイトのゼロパディング、及びIPv6とTCPの間に拡張ヘッダが存在する場合にIPv6ヘッダ値とは異なる次のヘッダ値を含む。
TCPチェックサムは決してオプションではない。送信者はそれを生成しなければならず(MUST-2)、受信者はそれをチェックしなければならない(MUST-3)。💡 チェックサムの計算について
送信側:
- チェックサム格納領域をゼロ埋め
- チェックサム計算対象領域で16ビットごとに1の補数和を行う
- 計算結果の1の補数をチェックサム格納領域に入れる
受信側:
- チェックサム計算対象領域で16ビットごとに1の補数和を行う
- 計算結果が0(全ビットが1)となることを確認する
16ビットの数値Aの1の補数は、0xffff-A、すなわちビット反転でもある。1の補数和とは、足し算の結果のMSB側の繰り上がりをLSBに加算することで計算できる。この方法は、バイトオーダー(エンディアン)が異なっても、桁溢れた巡回するためどちらも同じ値になる。
計算の説明のために、以下のIPパケットでチェックサムの計算をしてみる。
45 00 00 34 00 00 40 00 40 06 d7 43 c0 a8 00 0b 11 39 91 94
まず2バイトずつ計算する
0x4500 + 0x0034 = 0x4534
0x4534 + 0x0000 = 0x4534
0x4534 + 0x4000 = 0x8534
0x8534 + 0x4006 = 0xc53a
0xd743はIPチェックサムなので飛ばす。
0xc53a + 0xc0a8 = 0x185e2 → 0x85e3 (桁が上がったら上がった分を1の位に加算して16ビット化)
0x85e3 + 0x000b = 0x85ee
0x85ee + 0x1139 = 0x9727
0x9727 + 0x9194 = 0x128bb → 0x28bc
0x28bcを反転(1の補数)させると、0xd743になる。 -
緊急ポインタ: 16ビット
このフィールドは、このセグメントのシーケンス番号からの正のオフセットとして、緊急ポインタの現在値を伝える。緊急ポインタは、緊急データに続くオクテットのシーケンス番号を指す。このフィールドは、URG制御ビットが設定されたセグメントでのみ解釈される。 -
オプション:
[TCPオプション]; サイズ(オプション) == (DOffset-5)*32; DOffset > 5 の場合にのみ存在する。このサイズ式には、実際に存在するオプションの末尾のパディングも含まれることに留意すること。💡 TCPヘッダ(オプション含む)
オプションを含んだTCPヘッダ。
オプションは、TCPヘッダの末尾のスペースを占有する可能性があり、長さは8ビットの倍数になる。すべてのオプションはチェックサムの計算対象である。オプションは任意のオクテット境界で開始することができる。オプションの形式には2つのケースがある:
- ケース1: オプション種別を表す1オクテット。
- ケース2: オプション種別のオクテット、オプション長のオクテット、及び実際のオプション・データのオクテット。
オプション長は、オプションの種類とオプション長の2オクテットと、オプション・データのオクテットをカウントする。
オプション・リストは、データ・オフセット・フィールドが示すよりも短い場合があることに留意する。オプション・リストの終了オプション以降のヘッダの内容は、ゼロのヘッダ・パディングでなければならない(MUST-69)。
現在定義されているすべてのオプション・リストはIANA[62]で管理されており、各オプションはそこで示されている他のRFCで定義されている。このセットには、複数の同時使用をサポートするために拡張できる実験的なオプションも含まれる[45]。
あるTCP実装では、現在定義されているすべてのオプションをサポートできるが、以下のオプションをサポートしなければならない(MUST-4 ─ 最大セグメント・サイズ・オプションのサポートもセクション3.7.1のMUST-14の一部であることに留意する):種類 長さ 意味 0 ─ オプション・リストの終了 1 ─ 何もしない(ノー・オペレーション) 2 4 最大セグメント・サイズ 表1: 必須オプション・セット
これらのオプションの詳細は、セクション3.2で規定する。
TCP実装は、任意のセグメントでもTCPオプションを受信できなければならない(MUST-5)。
TCP実装は、オプションに長さフィールドがあると仮定し、実装していないTCPオプションはエラーなしで無視しなければならない(MUST-6)。オプション・リストの終了オプション(EOL)とノー・オペレーション(NOP)を除くすべてのTCPオプションは、将来のすべてのオプションを含めて、長さフィールドを持たなければならない(MUST-68)。TCP実装は、不正なオプション長(例えばゼロ)を処理する準備をしなければならない、推奨される手順は、コネクションをリセットして、エラー原因をログに記録する(MUST-7)。
注: [65]のように、TCPオプションに利用できる領域を拡張する作業が進行中である。 -
データ: 可変長
TCPセグメントによって伝送されるユーザデータ。
3.2. 特定のオプションの定義
必須オプション・セットのTCPオプションは、オプション・リスト終了オプション、ノー・オペレーション・オプション、または最大セグメント・サイズ・オプションのいずれかである。
オプション・リスト終了オプションのフォーマットは以下のとおり:
ここで、
種別: 1バイト; 種別 == 0。
このオプション・コードは、オプション・リストの終わりを示す。データ・オフセット・フィールドに従って、TCPヘッダの終わりと一致しないかも知れない。これは、各オプションの末尾ではなく、すべてのオプションの末尾で使用され、オプションの末尾がTCPヘッダの末尾と一致しない場合にのみ使用する必要がある。
ノー・オペレーション・オプションのフォーマットは以下のとおりである:
ここで、
種別: 1バイト; 種別 == 1。
このオプション・コードは、例えば、後続のオプションの先頭をワード境界に揃えるために、オプション間で使用することができる。送信者がこのオプションを使うという保証はないため、受信側は、オプションがワード境界で始まらなくても処理できるように準備しなければならない(MUST-64)。
最大セグメント・サイズ・オプションのフォーマットは以下の通りである:
ここで、
種別: 1バイト; 種別 == 2。
このオプションが存在する場合、このセグメントを送信するTCPエンドポイントの最大受信セグメント・サイズを伝達する。この値は、IPの再組み立て制限によって制限される。このフィールドは、初期接続要求(つまり、SYN制御ビットがセットされたセグメント)で送信できる、他のセグメントでは送信してはならない(MUST-65)。このオプションを使用しない場合は、任意のセグメント・サイズが許可される。このオプションのより詳しい説明はセクション3.7.1に記載する。
長さ: 1バイト; 長さ == 4。
オプションの長さ(バイト単位)。
最大セグメント・サイズ(MSS): 2バイト。
このセグメントを送信するTCPエンドポイントの最大受信セグメント・サイズ。
3.2.1. 他の一般的なオプション
追加のRFCには、高い性能を実現するために実装することが推奨されるが、基本的なTCPの相互運用性には必要ない、他の一般的に使用されるオプションを定義している。これらは、TCP選択応答(SACK)オプション[22] [26]、TCPタイムスタンプ(TS)オプション[47]、TCPウィンドウ・スケール(WS)オプション[47]がある。
3.2.2.実験的TCPオプション
実験的なTCPオプションの値は[30]で定義されており、[45]ではこれらの実験的な値の現在推奨される使用方法を説明している。
3.3. TCP 用語の概要
このセクションでは、本文書の残りの部分の詳細なプロトコル動作を理解するために必要な重要な用語の概要を説明する。セクション4に用語集がある。
3.3.1. 主要なコネクション状態変数
TCP実装の動作を詳しく説明する前に、いくつかの詳細な用語を紹介する必要がある。TCPコネクションを維持するには、いくつかの変数の状態を維持する必要がある。これらの変数は、トランスミッション・コントロール・ブロック(TCB)と呼ばれるコネクション・レコードに格納されると考えられる。TCBに格納される変数には、ローカルとリモートのIPアドレスとポート番号、IPセキュリティ・レベル、接続のコンパートメント(付録A.1を参照)、ユーザの送受信バッファへのポインタ、再送信キューへのポインタと現在のセグメントへのポインタがある。さらに、送受信シーケンス番号に関連するいくつかの変数がTCBに格納される。
⚠️ TCBについて
TCPは個別にコネクションを管理するため、各コネクションごとにフローの状態を記録する変数を定義している。要するに、TCPは自分が送信し、確認応答を受信したデータのシーケンスを追跡しなければならない。これを実現するために、TCBと呼ばれる特別なデータ構造が、オープンされたコネクションごとに初期化されている。
OpenBSDでは、PCBを構造体として保持している(<https://github.com/openbsd/src/blob/master/sys/netinet/tcp_var.h>)。
struct tcpcb {
struct tcpqehead t_segq; /* sequencing queue */
struct timeout t_timer[TCPT_NTIMERS]; /* tcp timers */
short t_state; /* state of this connection */ ...
...
/*
* The following fields are used as in the protocol specification.
* See RFC793, Dec. 1981, page 21.
*/
/* send sequence variables */
tcp_seq snd_una; /* send unacknowledged */
tcp_seq snd_nxt; /* send next */
tcp_seq snd_up; /* send urgent pointer */
tcp_seq snd_wl1; /* window update seg seq number */
tcp_seq snd_wl2; /* window update seg ack number */
tcp_seq iss; /* initial send sequence number */
u_long snd_wnd; /* send window */
int sack_enable; /* enable SACK for this connection */
int snd_numholes; /* number of holes seen by sender */
struct sackhole *snd_holes; /* linked list of holes (sorted) */
tcp_seq snd_last; /* for use in fast recovery */
/* receive sequence variables */
u_long rcv_wnd; /* receive window */
tcp_seq rcv_nxt; /* receive next */
tcp_seq rcv_up; /* receive urgent pointer */
tcp_seq irs; /* initial receive sequence number */
tcp_seq rcv_lastsack; /* last seq number(+1) sack'd by rcv'r*/
int rcv_numsacks; /* # distinct sack blks present */
struct sackblk sackblks[MAX_SACK_BLKS]; /* seq nos. of sack blocks */
...
};
変数 | 説明 |
---|---|
SND.UNA | ACK未受信の送信済みデータの領域の先頭 |
SND.NXT | 次に送信するデータの先頭 |
SND.WND | 送信ウィンドウ・サイズ |
SND.UP | 送信緊急ポインタ |
SND.WL1 | 直近のウインドウ更新でのセグメントのシーケンス番号 |
SND.WL2 | 直近のウインドウ更新でのセグメントの確認応答番号 |
ISS | 送信シーケンス番号の初期値 |
表2: 送信シーケンス変数
変数 | 説明 |
---|---|
RCV.NXT | 次を受信するセグメントのシーケンス番号の期待値 |
RCV.WND | 受信ウィンドウ・サイズ |
RCV.UP | 受信緊急ポインタ |
IRS | 受信シーケンス番号の初期値 |
表3: 受信シーケンス変数
以下の図は、これらの変数の一部をシーケンス領域に関連付けるのに役立つだろう。
図3: 送信シーケンス領域
送信ウィンドウは、図3で3とラベル付けされたシーケンス領域部分である。
⚠️ 図3について
図3は、かなり簡略化されている。バイトストリームでイメージすると以下の図になる。
⚠️ 実際のデータ送信(パケットキャプチャ)
コネクションが確立されると、データの送信処理のため、以下のTCBの3つの変数(SND.NXT、RCV.NXT、SND.UMA)は状態の追跡に重要である。例えば、送信者Aが受信者Bにセグメントを送信すると、次のことが起こる。
- Aはセグメントを送信し、自身のレコードでSND.NXTを進める
- Bはセグメントを受信し、RCV.NXTを進めることでACKを送信する
- AはACKを受信し、SND.UNAを進める
TCPdumpで見ると次のようになる(A: 192.168.0.9、B: 192.168.0.11)。変数を進める量は、セグメントのデータ長である。
% tcpdump -i any port 10000 -nt
IP 192.168.0.9.51969 > 192.168.0.11.10000: Flags [SEW], seq 4095558142, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 3596123066 ecr 0,sackOK,eol], length 0
IP 192.168.0.11.10000 > 192.168.0.9.51969: Flags [S.E], seq 3273273133, ack 4095558143, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 3101696552 ecr 3596123066,sackOK,eol], length 0
IP 192.168.0.9.51969 > 192.168.0.11.10000: Flags [.], ack 1, win 2058, options [nop,nop,TS val 3596123079 ecr 3101696552], length 0
IP 192.168.0.11.10000 > 192.168.0.9.51969: Flags [.], ack 1, win 2058, options [nop,nop,TS val 3101696556 ecr 3596123079], length 0
# 初期シーケンス番号は、Aは4095558142、Bは3273273133
IP 192.168.0.9.51969 > 192.168.0.11.10000: Flags [P.], seq 1:34, ack 1, win 2058, options [nop,nop,TS val 3596123079 ecr 3101696552], length 33
IP 192.168.0.11.10000 > 192.168.0.9.51969: Flags [.], ack 34, win 2058, options [nop,nop,TS val 3101696556 ecr 3596123079], length 0
# Aは33バイトのデータを持つセグメントを送信し、BはACKセグメントでそれを確認する。
# tcpdump読みやすくするために、相対シーケンス番号がデフォルトで表示している。
# したがって、ack 34は、実際には4095558142 + 33となる。
#内部的には、BのTCPはRCV.NXTを33で進めている。
# 長さ0のセグメントの確認応答シーケンス番号は、前に受信したセグメント長に基づいて
# インクリメントされる。
# AはFINセグメントを生成し、送信するデータもうないことをホストBに通知する(Bも同様)
IP 192.168.0.9.51969 > 192.168.0.11.10000: Flags [F.], seq 34, ack 1, win 2058, options [nop,nop,TS val 3596123079 ecr 3101696552], length 0
IP 192.168.0.11.10000 > 192.168.0.9.51969: Flags [.], ack 35, win 2058, options [nop,nop,TS val 3101696557 ecr 3596123079], length 0
IP 192.168.0.11.10000 > 192.168.0.9.51969: Flags [F.], seq 1, ack 35, win 2058, options [nop,nop,TS val 3101696557 ecr 3596123079], length 0
IP 192.168.0.9.51969 > 192.168.0.11.10000: Flags [.], ack 2, win 2058, options [nop,nop,TS val 3596123084 ecr 3101696557], length 0
図4: 受信シーケンス領域
受信ウィンドウは、図4で2とラベル付けされたシーケンス領域部分である。
⚠️ 図4について
図4は、かなり簡略化されている。バイトストリームでイメージすると以下の図になる。
また、現在のセグメントのフィールドから値を取得する説明の中で頻繁に使用される変数がいくつかある。
変数 | 説明 |
---|---|
SEG.SEQ | セグメントの送信シーケンス番号 |
SEG.ACK | セグメントの受信順序番号 |
SEG.LEN | セグメント長 |
SEG.WND | セグメントのウィンドウ値 |
SEG.UP | セグメントの緊急ポインタ(*) |
表4: 現在のセグメント変数
3.3.2. ステートマシンの概要
コネクションは、その存続期間中に一連の状態を経て進行する。状態は、LISTEN、SYN-SENT、SYN-RECEIVED、ESTABLISHED、FIN-WAIT-1、FIN-WAIT-2、CLOSE-WAIT、CLOSING、LAST-ACK、TIME-WAIT、そして架空の状態であるCLOSEDである。CLOSEDは架空の状態だが、これはTCBが存在せず、したがってコネクションがない状態を表すからである。各状態の意味を簡単に説明すると、
LISTEN ─ 任意のリモートTCPピアとポートからのコネクション要求を待機する状態を表す。
SYN-SENT ─ コネクション要求を送信した後、一致するコネクション要求を待機する状態を表す。
SYN-RECEIVED ─ コネクション要求の受信と送信の両方を行った後、コネクション要求の確認応答を待機する状態を表す。
ESTABLISHED ─ オープンなコネクションを表し、受信したデータはユーザに配信できる。コネクションのデータ転送フェーズの通常状態である。
FIN-WAIT-1 ─ リモートTCPピアからのコネクション終了要求、または前に送られたコネクション終了要求の確認応答を待機する状態を表す。
FIN-WAIT-2 ─ リモートTCPピアからのコネクション終了要求を待機する状態を表す。
CLOSE-WAIT ─ ローカルユーザからのコネクション終了要求を待機する状態を表す。
CLOSING ─ リモートTCPピアからのコネクション終了要求の確認応答を待機する状態を表す。
LAST-ACK ─ 以前にリモートTCPピアに送信されたコネクション終了要求の確認応答を待機する状態を表す(リモートTCPピアに送信されたこの終了要求には、リモートTCPピアから送信された終了要求の確認応答がすでに含まれていた)。
TIME-WAIT ─ リモートTCPピアがコネクション終了要求の確認応答を受信したことを確認し、前のコネクションからの遅延セグメントによって新しいコネクションが影響を受けるのを避けるために、十分な時間が経過するのを待つ状態を表す。
CLOSED ─ コネクション状態がまったくない状態を表す。
TCPのコネクションはイベントに応じて、ある状態から別の状態へと進行する。イベントとは、OPEN、SEND、RECEIVE、CLOSE、ABORT、およびSTATUSというユーザコール、受信セグメント、特にSYN、ACK、RST、FINフラグを含むセグメント、及びタイムアウトである。
OPENコールは、コネクション確立を能動的に行うか、受動的に待機するかを指定する。
受動的なOPEN要求は、コネクションを開始しようとする能動的なOPENとは対照的に、プロセスが着信コネクション要求を受け入れたいことを意味する。
図5の状態図は、状態変化のみを、原因となるイベントとその結果のアクションとともに示しているが、エラー状態も、状態変化と関係のないアクションも扱っていない。後のセクションで、イベントに対するTCP実装の反応について詳しく説明する。一部の状態名は、図内では文書内の他の場所での表示とは異なる方法で略称やハイフンで表記されている。
NOTA BENE(注):
この図は略図に過ぎず、仕様の全体像として捉えてはならない。多くの詳細が含まれていない。
図5: TCPコネクション状態遷移図
次の注記が図5に適用される:
注記1: RST受信時のSYN-RECEIVEDからLISTENへの遷移は、受動的なOPENの後にSYN-RECEIVEDに到達していることが条件となる。
注記2: 図では、FINを受信し、ローカルのFINも確認された場合のFIN-WAIT-1からTIME-WAITへの遷移を省略している。
注記3: RSTは、対応するTIME-WAITへの遷移を伴う任意の状態から送ることができる(理論的根拠については[70]参照)。これらの遷移は明示的には示されていない。そうでなければ、この図が非常に読みにくくなってしまう。同様に、いずれかの状態からでもRSTを受信すると、LISTENまたはCLOSEDに遷移するが、これも読みやすくするために図では省略している。
3.4. シーケンス番号
設計における基本的な概念は、TCPコネクション上で送信されるデータの各オクテットにはシーケンス番号があるということである。各オクテットはシーケンス番号を持っているので、それぞれは確認応答が可能である。採用されている確認応答メカニズムは累積的であり、シーケンス番号Xの確認応答は、Xまでのすべてのオクテットを受信したことを示す。このメカニズムにより、再送があっても重複を簡単に検出できる。セグメント内のオクテットの番号付けスキームは以下のとおりである。ヘッダの直後の最初のデータ・オクテットが最も小さい番号が付けられ、後続のオクテットは連続して番号が付けられる。
実際のシーケンス番号領域は大きいとはいえ、有限であることを忘れてはならない。この領域は0~
TCP実装が実行しなければならない典型的なシーケンス番号の比較には、以下のようなものがある:
(a) 確認応答が、送信されたがまだ確認されていないシーケンス番号を指していると判断する。
(b) セグメントによって占められているすべてのシーケンス番号が確認応答されたと判断する(例えば、再送キューからセグメントを削除する)。
(c) 受信セグメントに予期されるシーケンス番号が含まれている (つまり、セグメントが受信ウィンドウと「オーバーラップ」している)と判断する。
データの送信に応答して、TCPエンドポイントは確認応答を受け取る。確認応答を処理するには、以下の比較が必要である:
SND.UNA = 最も古い未確認のシーケンス番号
SND.NXT = 次に送信されるシーケンス番号
SEG.ACK = 受信側TCPピアからの確認応答(受信側TCPピアが期待する次のシーケンス番号)
SEG.SEQ = セグメントの初期シーケンス番号
SEG.LEN = セグメントのデータが占めるオクテット数(SYNとFINを数える)
SEG.SEQ+SEG.LEN-1 = セグメントの最後のシーケンス番号
新しい確認応答(「受け入れ可能な確認応答」と呼ばれる)は、以下の不等式が成り立つ:
SND.UNA < SEG.ACK =< SND.NXT
再送キューにあるセグメントは、そのシーケンス番号と長さの合計が、受信セグメントの確認応答値以下であれば、完全に確認応答されたことになる。
データを受信すると、以下の比較が必要となる:
- RCV.NXT = 受信セグメントで予期される次のシーケンス番号。受信ウィンドウの左端または下端である
- RCV.NXT+RCV.WND-1 = 受信セグメントで予期される最後のシーケンス番号であり、受信ウィンドウの右端または上端である
- SEG.SEQ = 受信セグメントが占める初期シーケンス番号
- SEG.SEQ+SEG.LEN-1 = 受信セグメントが占有する最後のシーケンス番号
RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND
または
RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND
の場合、セグメントは有効な受信シーケンス領域の一部を占有していると判断する。
このテストの最初の部分では、セグメントの先頭がウィンドウ内に収まるかどうかを確認し、テストの2番目の部分では、セグメントの末尾がウィンドウ内に収まるかどうかを確認する。セグメントがいずれかのテストに合格すれば、ウィンドウ内にデータが含まれていることになる。
実際には、もう少し複雑である。ウィンドウがゼロで、セグメントの長さがゼロであるため、受信セグメントが受け入れられるかどうかについては、4つのケースがある:
セグメント の長さ |
受信 ウィンドウ |
テスト |
---|---|---|
0 | 0 | SEG.SEQ = RCV.NXT |
0 | >0 | RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND |
>0 | 0 | 受け入れられない |
>0 | >0 | RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND または RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND |
表5: セグメントの許容性テスト
受信ウィンドウがゼロの場合、ACKセグメントを除き、いかなるセグメントも受け入れられないことに留意する。したがって、TCP実装では、データを送信し、ACKを受信している間、受信ウィンドウをゼロに維持することが可能である。TCP受信者は、受信ウィンドウがゼロの場合でも、すべての受信セグメントのRSTフィールドとURGフィールドを処理しなければならない(MUST-66)。
私たちは、特定の制御情報を保護するために、番号付けスキームを利用した。これは、いくつかの制御フラグを暗黙のうちにシーケンス領域に含めることで、混乱なく再送と確認ができるようにすることで達成される(つまり、制御のコピーは唯一1つだけしか動作しない)。制御情報はセグメントデータ領域には物理的に格納されない。したがって、制御に暗黙的にシーケンス番号を割り当てるルールを採用しなければならない。SYNとFINだけが、この保護が必要な唯一の制御であり、これらの制御はコネクションの開閉時にのみ使用される。シーケンス番号の目的上、SYNはそれが発生するセグメントの最初の実データ・オクテットの前に発生すると考えられるが、FINはそれが発生するセグメントの最後の実際のデータ・オクテットの後に発生すると考えられる。セグメント長(SEG.LEN)は、データとシーケンスの領域占有制御の両方を含む。SYNが存在する場合、SEG.SEQはSYNのシーケンス番号である。
3.4.1.初期シーケンス番号の選択
コネクションはソケットのペアで定義する。コネクションは再利用できる。コネクションの新しいインスタンスは、コネクションのインカーネーション(転生)と呼ばれる。ここから生じる問題は、「TCPの実装は、コネクションの前のバージョンと重複するセグメントをどのように識別するのか?」ということである。この問題は、コネクションのオープンとクローズが立て続けに行われる場合、あるいはコネクションがメモリの損失によって切断され、その後再確立する場合に明らかになる。これをサポートするために、TIME-WAIT状態はコネクションの再利用率を制限し、後述する初期シーケンス番号の選択により、受信パケットがどのコネクションの転生に対応するかという曖昧さからさらに保護している。
混乱を避けるため、同じシーケンス番号が前の転生のネットワークに存在する可能性がある間に、あるコネクションの転生のセグメントが利用されるのを防がなければならない。TCPエンドポイントが、それまで使用していたシーケンス番号に関する情報をすべて失った場合でも、これを保証したい。新しいコネクションが作成されるとき、新しい32ビットの初期シーケンス番号(ISN)を選択するISN生成器を使用する。パス外の攻撃者がISN値を予測または推測できる場合、セキュリティ上の問題が発生する[42]。
TCPの初期シーケンス番号は、ラップするまで単調に増加する数値シーケンスから生成され、大まかに「クロック」として知られている。このクロックは32ビットのカウンタであり、通常はおよそ4マイクロ秒ごとに少なくとも1回ずつインクリメントするが、リアルタイムであることも正確であることも想定されておらず、再起動後も維持する必要はない。このクロック・コンポーネントは、最大セグメント寿命(MSL)を使用すると、MSLよりもはるかに長い約4.55時間ごとにサイクルすることから、生成されるISNが一意になるようにすることを目的としている。コネクションが開始され、シーケンス番号がMSL内で重複するように急速に進むことに留意し、高データレートをサポートする最新のネットワークでは、セクション3.4.3で後述するタイムスタンプ・オプションを実装することを推奨する。
TCP実装は、初期シーケンス番号のクロック駆動選択に上記のタイプの「クロック」を使用しなければならず(MUST-8)、以下の式を使用して初期シーケンス番号を生成する必要がある。
ISN = M + F(localip, localport, remoteip, remoteport, secretkey)
ここで、Mは4マイクロ秒のタイマーで、F()
はコネクションの識別パラメータ(localip, localport, remoteip, remoteport
) と秘密鍵 (secretkey
)の擬似乱数関数(PRF)である(SHLD-1)。F()は外部から計算可能であってはならない(MUST-9)。さもないと、攻撃者が他のコネクションに使用されたISNからシーケンス番号を推測することができる。PRFは、TCPコネクション・パラメータと秘密データを連結した暗号ハッシュとして実装することもできる。特定のハッシュ・アルゴリズムの選択と秘密鍵データの管理については、[42]のセクション3を参照のこと。
各コネクションには、送信シーケンス番号と受信シーケンス番号がある。初期送信シーケンス番号(ISS)はデータを送信するTCPピアによって選択され、初期受信シーケンス番号(IRS)はコネクション確立手順中に学習される。
コネクションを確立または初期化するには、2つのTCPピアが互いの初期シーケンス番号を同期する必要がある。これは、「SYN」(同期の意味)と呼ばれる制御ビットと初期シーケンス番号を持つコネクション確立セグメントを交換することで行われる。略記として、SYNビットを持つセグメントは「SYN」とも呼ぶ。したがって、このソリューションには、初期シーケンス番号を選択するための適切なメカニズムと、ISNを交換するためのわずかに複雑なハンドシェイクが必要である。
同期には、各側が自身の初期シーケンス番号を送信し、リモートTCPピアから確認応答としてその確認を受信する必要がある。また、各側はリモートピアの初期シーケンス番号を受信し、確認応答を送信しなければならない。
- A --> B SYN 私のシーケンス番号はXである
- A <-- B ACK あなたのシーケンス番号はXである
- A <-- B SYN 私のシーケンス番号はYである
- A --> B あなたのシーケンス番号はYであることを確認する
ステップ2と3を1つのメッセージにまとめることができるため、これはスリーウェイ(またはスリーメッセージ)ハンドシェイク(3WHS)と呼ばれる。
シーケンス番号がネットワーク内のグローバル・クロックに結び付いておらず、TCP実装がISNを選択するための異なるメカニズムが持っているかもしれないため、3WHSが必要である。最初のSYNの受信者は、そのコネクションで最後に使用されたシーケンス番号(常に可能とは限らない)を覚えていない限り、そのセグメントが古いものかどうかを知る方法がないため、送信者にこれを確認するように要求する必要がある。スリーウェイ・ハンドシェイクと、ISN選択のためのクロック駆動方式の利点については、[69]で説明している。
3.4.2. いつ沈黙を保つかを知る
同じポート番号とシーケンス領域が再利用された場合、ネットワーク内の古いセグメントとホストの再起動後の新しいセグメントが混同され、データが破損する可能性があるという理論上の問題が存在する。後述する「平穏時」(quiet time)の概念はこれに対処するものであり、現在の実装ではほとんど必要ないと思われるが、関連する可能性がある状況のために、この概念についての説明を行う。この問題は、TCPの歴史が浅いほど関連性が高かった。今日のインターネットにおける実用的な使用では、エラーが発生しやすい状況は十分に低いため、無視しても問題ない。この問題が無視できるようになった理由としては、以下のようなものがある: (a) ISSとエフェメラル・ポートのランダム化により、再起動後にポート番号とシーケンス番号が再利用される可能性が低くなったこと、(b) リンクの高速化に伴い、インターネットの実効MSLが低下したこと、(c) いずれにせよ、再起動にMSL以上の時間がかかることが多い。
TCP実装が、ネットワークに残っている古いセグメントと重複する可能性のあるシーケンス番号を持つセグメントを作成しないことを保証するため、TCPエンドポイントは、起動時または使用中のシーケンス番号のメモリが失われた状況からの回復時、シーケンス番号を割り当てる前に、MSLの間、沈黙を保たなければならない。この仕様では、MSLを2分とする。これはエンジニアリング上の選択であり、経験上そうすることが望ましいことが示された場合は変更する可能性がある。TCPエンドポイントが何らかの意味で再初期化されても、使用中のシーケンス番号のメモリを保持している場合、まったく待つ必要がないことに留意する。最近使用したシーケンス番号よりも大きいシーケンス番号を使用するようにしなければならない。
3.4.3. TCP平穏時の概念
何らかの理由で、アクティブな(つまり、閉じていない)コネクションで最後に送信されたシーケンス番号の情報を失ったホストは、そのホストが属するインターネット・システムで少なくとも合意されたMSL以上、TCPセグメントの送信を遅らせなければならない。以下の段落で、この仕様について説明する。TCPの実装者は「平穏時」の制限に違反する可能性はあるが、その場合、インターネット・システムの一部の受信者によって、一部の古いデータが新しいデータとして受け入れられたり、新しいデータが古い重複データとして拒否されたりするリスクが生じるだけである。
TCPエンドポイントは、セグメントが形成され、送信元ホストのネットワーク出力キューに入力されるたびに、シーケンス番号領域を消費する。TCPの重複検出とシーケンス・アルゴリズムは、シーケンス番号にバインドされたセグメント・データが受信者に配信され確認され、セグメントの重複コピーがすべてインターネットから「排出(drained)」する前に、シーケンス番号がすべて
通常の状況では、TCP実装は、最初の使用されたシーケンス番号が確認される前に誤って再利用されないように、次に発行されるシーケンス番号と確認待ちの最も古いシーケンス番号を追跡する。これだけでは、古い重複データがネットから排出されたことを保証できないため、さまよう重複データが到着時に問題を引き起こす可能性を減らすために、シーケンス領域は大きくなる。2メガビット/秒では、
しかし、送信元のTCPエンドポイントがあるコネクションで最後に使用したシーケンス番号のメモリを持たない場合、TCPの基本的な重複検出とシーケンスのアルゴリズムは破られている可能性がある。例えば、TCP実装がすべてのコネクションをシーケンス番号0で開始する場合、ホストを再起動すると、TCPピアは以前のコネクションを再形成し(おそらくハーフ・オープン・コネクションの解決後)、まだネットワークに残っているパケットと同じ、あるいは重複するシーケンス番号のパケットを発し、そのパケットは同じコネクションの前のインカーネーションで発行されたものである。特定のコネクションで使用されたシーケンス番号に関する知識がない場合、TCP仕様では、以前の接続のインカーネーションからのセグメントがシステムから排出される時間を確保するために、接続上でセグメントを送信する前にソースが MSL秒間遅延することを推奨しています。
時刻を記憶し、初期シーケンス番号値を選択するためにその時刻を使用できるホストでさえ、この問題を免れることはできない(つまり、新しいコネクションが発生するたびに初期シーケンス番号を選択するために時刻が使用されたとしても)。
例えば、シーケンス番号Sで始まるコネクションがオープンされたとする。このコネクションはあまり使用されず、最終的に初期シーケンス番号関数(ISN(t)
)が、このTCPエンドポイントの特定のコネクションで送信された最後のセグメントのシーケンス番号(例えばS1
)に等しい値になったと仮定する。ここで、この瞬間にホストが再起動し、新しいコネクションを確立したとする。最初に選択されるシーケンス番号は、S1 = ISN(t)
─ 古いコネクションで最後に使用されたシーケンス番号である! リカバリが迅速に行われた場合、S1
の近傍のシーケンス番号を持つネット内の古い重複が到着し、新しいコネクションのインカーネーションの受信者で新しいパケットとして扱われる可能性がある。
問題は、リカバリ中のホストが、再起動するまでのダウン時間を知らない可能性があり、また、以前のコネクションのインカーネーションの古い重複がシステムにまだ残っているかどうかも分からない。
この問題に対処する1つの方法は、再起動から回復後、1 MSLだけセグメントの送出を意図的に遅らせることである ─ これが「平穏時」(quiet time)の仕様である。待ち時間を避けたいホストや、ある送信先で古いパケットと新しいパケットが混同される可能性があるリスクを許容するホストは、「平穏時」を待たないことを選択できる。実装者は、再起動後に待機するかどうかをコネクションごとに選択できる機能を、TCPユーザに提供することも、すべてのコネクションに対して非公式に「平穏時」を実装してもよい。明らかに、ユーザが「待機」を選択した場合でも、ホストが少なくともMSL秒以上「稼働」していれば、その必要はない。
要約すると、発行されたすべてのセグメントは、シーケンス領域の1つ以上のシーケンス番号を占有し、セグメントが占有する番号は、MSL秒が経過するまで「ビジー」または「使用中」となる。再起動すると、まだ送信中の可能性のあるセグメントのオクテットとSYNまたはFINフラグによって時空のブロックが占有される。新しいコネクションの開始が早過ぎて、以前のコネクションでまだ送信中の可能性のあるセグメントの時領域フットプリント内のシーケンス番号のいずれかを使用された場合、潜在的なシーケンス番号の重複領域が存在し、受信側で混乱を引き起こす可能性がある。
ハイパフォーマンスのケースでは、上述の基本TCP設計で考慮する1秒あたりのメガビット数よりもサイクルタイムが短くなる。1 Gbpsの場合、サイクルタイムは34秒だが、10 Gbpsではわずか3秒、100 Gbpsでは約3分の1秒である。このようなハイパフォーマンスのケースでは、TCPタイムスタンプ・オプションとラップされたシーケンスに対する保護 (PAWS)[47]が、古い重複を検出して破棄するために必要な機能を提供する。
3.5. コネクションを確立する
「スリーウェイ・ハンドシェイク」は、コネクションを確立するために使用される手順である。この手順は通常、一方のTCPピアが開始し、もう一方のTCPピアが応答する。この手順は、2つのTCPピアが同時に手順を開始した場合にも機能する。同時オープンが発生すると、各TCPピアはSYNを送信した後、確認応答を含まないSYNセグメントを受信する。もちろん、古い重複したSYNセグメントが到着すると、受信者からは同時コネクションの開始が進行中であるように見える可能性がある。「リセット」セグメントを適切に使用することで、このようなケースを区別することができる。
以下に接続開始の例をいくつか示す。これらの例では、データ伝送セグメントを使用したコネクションの同期を示していないが、受信側のTCPエンドポイントが、データが有効であることが明らかになるまで、ユーザにデータを配信しない限り(例えば、スリーウェイ・ハンドシェイクによって誤ったコネクションの可能性が低くなることを考慮して、コネクションがESTABLISHED状態に達するまで受信側でデータをバッファする)、これは完全に正しい。このチェックのための情報を提供するために、メモリとメッセージの間のトレードオフの関係にある。
最も単純な3WHSを図6に示す。この図は次のように解釈する。各行には参照用番号が振られている。右矢印 (-->
) は、TCPピアAからTCPピアBへのTCPセグメントの発出、またはAからBへのセグメントの到着を示す。左矢印(<--
)はその逆を示す。省略記号 (...
)は、ネットワークにまだ残っている(遅延している)セグメントを示す。括弧内はコメントである。TCPコネクションの状態は、セグメントの発出または到着後の状態を表す(セグメントの内容は各行の中央に示されている)。セグメントの内容は、シーケンス番号、制御フラグ、ACKフィールドを省略形で示す。ウィンドウ、アドレス、長さ、テキストなどの他のフィールドは、分かりやすくするために省略している。
図6: コネクション同期のための基本的なスリーウェイ・ハンドシェイク
図6の2行目で、TCPピアAは、シーケンス番号100から始まるシーケンス番号を使用することを示すSYNセグメントを送信することから始める。3行目では、TCPピアBがSYNを送信し、TCPピアAから受信したSYNを確認応答する。確認応答フィールドは、TCPピアBがシーケンス101を受信することを期待していることを示しており、シーケンス100を占有したSYNを確認応答していることを示していることに留意する。
4行目で、TCPピアAは、TCPピアBのSYNに対するACKを含む空のセグメントで応答する。5行目で、TCPピアAがデータを送信する。ACKがシーケンス番号領域を占有していないため、5行目のセグメントのシーケンス番号が4行目と同じであることに留意する(もし占有していたら、ACKを確認応答してしまうことになる!)。
同時開始は図7に示すように、わずかに複雑になるだけである。各TCPピアのコネクション状態は、CLOSED→SYN-SENT→SYN-RECEIVED→ESTABLISHEDと循環する。
図7: 同時コネクションの同期
TCP実装は、同時オープン試行をサポートしなければならない(MUST-10)。
TCP実装は、パッシブOPENの結果として、コネクションがSYN-RECEIVED状態に達したかどうか、それともアクティブOPEN結果として、コネクションがSYN-RECEIVED状態に達したのかを追跡しなければならない(MUST-11)ことに留意する。
スリーウェイ・ハンドシェイクの主な理由は、古い重複したコネクション開始が混乱を引き起こすのを防ぐためである。これに対処するために、特別な制御メッセージであるリセットが規定されている。受信側のTCPピアが非同期状態(つまり、SYN-SENT、SYN-RECEIVED)にある場合、受け入れ可能なリセットを受信すると、LISTENに戻る。TCPピアが同期状態(ESTABLISHED、FIN-WAIT-1、FIN-WAIT-2、CLOSE-WAIT、CLOSING、LAST-ACK、TIME-WAIT)のいずれかにある場合、コネクションを中止して、ユーザに通知する。後者の場合については、後述の「ハーフオープン」コネクションで説明する。
図8: 古い重複SYNからのリカバリ
古い重複からのリカバリの簡単な例として、図8を考えてみる。3行目で、古い重複SYNがTCPピアBに到着する。TCPピアBは、これが古い重複であることを認識できないため、通常どおり応答する(4行目)。TCPピアAは、ACKフィールドが正しくないことを検出し、セグメントを信頼できるものにするためにSEQフィールドを選択してRST(リセット)を返す。TCPピアBは、RSTを受信すると、LISTEN状態に戻る。元のSYNが最終的に6行目に到達すると、同期は正常に進行する。6行目のSYNがRSTよりも先に到着していた場合、RSTが双方向に送信され、より複雑なやり取りが発生していた可能性がある。
3.5.1. ハーフオープン接続とその他の異常
確立されたコネクションは、TCPピアの一方が他方に気付かれずにコネクションを閉じたり中断したりした場合、あるいはコネクションの両端が、再起動するとメモリが失われるような障害や非同期状態になった場合、「ハーフオープン」と言われる。このようなコネクションは、どちらかの方向にデータを送信しようとすると、自動的にリセットされる。しかし、ハーフオープンのコネクションは珍しいと想定される。
サイトAでコネクションが存在しなくなった場合、サイトBのユーザがそのコネクション上でデータを送信しようとすると、サイトBのTCPエンドポイントがリセット制御メッセージを受信することになる。このようなメッセージは、サイトBのTCPエンドポイントに何か問題があることを示し、コネクションが中断することが想定される。
2つのユーザ・プロセスAとBが互いに通信しているときに、障害または再起動が発生し、AのTCP実装のメモリが失われたとする。AのTCP実装をサポートしているオペレーティング・システムによっては、何らかのエラー回復メカニズムが存在する可能性がある。TCPエンドポイントが再び起動すると、Aは最初から、または回復ポイントから再起動する可能性が高い。その結果、Aはおそらく再度コネクションをOPENしようとするか、オープンしたと思われるコネクションでSENDしようとする。後者の場合、ローカル(A)のTCP実装から「connection not open」というエラー・メッセージを受け取る。コネクションを確立しようとして、AのTCP実装はSYNを含むセグメントを送信する。このシナリオは、図9に示す例につながる。TCPピアAが再起動した後、ユーザは接続を再度オープンしようとする。その間に、TCPピアBはコネクションがオープンしていると考える。
図9: ハーフオープン接続の検出
SYNが3行目に到着すると、同期状態にあるTCPピアBとウィンドウ外の着信セグメントは、次にどのようなシーケンスを受信することになるかを示す確認応答(ACK 100)で応答する。TCPピアAは、このセグメントが送信したものを何も確認応答していないことを知り、同期が取れていないため、ハーフオープン・コネクションを検出したので、リセット(RST)を送信する。TCPピアBは5行目で中止する。TCPピアAは引き続きコネクションの確立しようとする。問題は、図6の基本的なスリーウェイ・ハンドシェイクにまとめられる。
TCPピアAが再起動し、TCPピアBが同期コネクションだと思ってデータを送信しようとした場合、興味深い別のケースが発生する。これを図10に示す。この場合、TCPピアBからTCPピアAに到着したデータ(2行目)は、そのようなコネクションが存在しないため受け入れられず、TCPピアAはRSTを送信する。RSTは受け入れられるため、TCPピアBはRSTを処理し、コネクションを中止する。
図10: アクティブ側がハーフオープン・コネクションの検出
図11では、SYN待ちのパッシブ接続を持つ2つのTCPピアAとBを示している。TCPピアBに到着した古い重複(2行目)が、Bの動作を引き起こす。SYN-ACKが返され(3行目)、TCP AはRSTを生成する(3行目のACKは受け入れられない)。TCPピアBはリセットを受け入れ、パッシブなLISTEN状態に戻る。
図11: 古い重複SYNが2つのパッシブ・ソケットでリセットを開始する
この他にもさまざまなケースが考えられるが、RSTの生成と処理に関する以下のルールですべて説明できる。
3.5.2.リセット生成
TCPユーザまたはアプリケーションは、いつでもコネクションにリセットを発行できるが、以下で説明するように、いくつかのエラー状態が発生した場合はプロトコル自体がリセット・イベントを生成する。リセットを発行するコネクション側は、TIME-WAIT状態に入る必要がある。これは、[70]で説明する理由により、ビジー状態のサーバの負荷を軽減するのに一般的に役立つ。
原則として、リセット(RST)は、明らかに現在のコネクションを対象としていないセグメントが到着するたびに送られる。これが当てはまるかどうかが明らかでない場合は、リセットを送信してはならない。
状態には3つのグループがある:
-
コネクションが存在しない(CLOSED)場合、他のリセットを除くすべての受信セグメントに応答してリセットが送られる。既存のコネクションと一致しないSYNセグメントは、この手段で拒否される。
受信セグメントにACKビットが設定されている場合、リセットはそのセグメントのACKフィールドからシーケンス番号を取得する。それ以外の場合、リセットはシーケンス番号は0を持ち、ACKフィールドを受信セグメントのシーケンス番号とセグメント長の合計に設定する。接続はCLOSED状態のままである。
-
コネクションが非同期状態(LISTEN、SYN-SENT、SYN-RECEIVED)にあり、受信セグメントがまだ送信されていないものを確認した(そのセグメントは受け入れ不可能なACKを伝送する)場合、あるいは受信セグメントがコネクションに対して要求されたレベルでコンパートメントに正確に一致しないセキュリティレベルかコンパートメント(付録A.1)を持つ場合、リセットが送られる。
受信セグメントがACKフィールドを持つ場合、リセットはそのセグメントのACKフィールドからシーケンス番号を取得する。それ以外の場合、リセットはシーケンス番号は0を持ち、ACKフィールドを受信セグメントのシーケンス番号とセグメント長の合計に設定する。コネクションは同じ状態のままである。
-
コネクションが同期状態(ESTABLISHED、FIN-WAIT-1、FIN-WAIT-2、CLOSE-WAIT、CLOSING、LAST-ACK、TIME-WAIT)にある場合、受け入れ不可能なセグメント(ウィンドウ外のシーケンス番号または受け入れ不可能な確認応答番号)対しては、現在の送信シーケンス番号と受信が期待される次のシーケンス番号を示す確認応答を含む空の(ユーザデータを含まない)確認応答セグメントで応答しなければならず、コネクションは同じ状態のままである。
受信セグメントのセキュリティレベルまたはコンパートメントが、そのコネクションに要求されたレベルでコンパートメントと正確に一致しない場合、リセットが送られ、コネクションはCLOSED状態になる。リセットは、受信セグメントのACKフィールドからシーケンス番号を取得する。
3.5.3.リセット処理
SYN-SENTを除くすべての状態において、すべてのリセット(RST)セグメントは、SEQフィールドをチェックすることで検証できる。リセットは、そのシーケンス番号がウィンドウ内にあれば有効である。SYN-SENT状態(最初のSYNに応答して受信したRST)では、ACKフィールドがSYNを受け入れていれば、RSTは受け入れられる。
RSTの受信者はまずRSTを検証し、次に状態を変更する。受信者がLISTEN状態にあった場合は、それを無視する。受信者がSYN-RECEIVED状態にあり、以前にLISTEN状態にあった場合、受信者はLISTEN状態に戻り、そうでなければ、受信者はコネクションを中止し、CLOSED状態に移行する。それ以外の状態にあった場合、コネクションを中止してユーザに通知し、CLOSED状態に移行する。
TCP実装は、受信したRSTセグメントにデータを含めることを許可すべきである(SHLD-2)。RSTセグメントには、RSTの原因を説明する診断データが含めることを推奨する。ただし、そのようなデータに関する標準はまだ確立されていない。
3.6. コネクションを閉じる
CLOSEは、「もう送信するデータがない」を意味する操作である。全二重コネクションを閉じるという概念は、受信側をどのように扱うか明らかではないため、曖昧な解釈の対象となる。私たちは、CLOSEをシンプレックス方式で処理することを選択した。CLOSEしたユーザは、TCP受信側にリモートピアもCLOSEDしたと伝えられるまで、RECEIVEを続けることができる。したがって、プログラムはいくつかのSENDの後にCLOSEを行い、リモートピアがCLOSEDしたためにRECEIVEが失敗したと伝えられるまでRECEIVEを続ける可能性がある。TCP実装は、たとえ未処理のRECEIVEがない場合でも、リモートピアがクローズしたことをユーザに通知するため、ユーザは自分の側を正常に終了することができる。TCP実装は、コネクションがCLOSEDになる前に送信されたすべてのバッファを確実に配送するため、何のデータも返ってこないと思っているユーザは、コネクションが正常にCLOSEDされたことを待つだけで、すべてのデータが送信先のTCPエンドポイントで受信されたことを知ることができる。ユーザは、TCP実装がデータがもうないことを示すまで、送信のために閉じたコネクションを読み続けなければならない。
基本的に3つのケースがある:
- ユーザがTCP実装にコネクションをCLOSEするように指示することで開始する(図12のTCPピアA)。
- リモートTCPエンドポイントがFIN制御信号を送信することで開始する(図12のTCPピアB)。
- 両方のユーザが同時にCLOSEする(図13)。
ケース1: ローカルユーザがCLOSEを開始する
この場合、FINセグメントを構築し、送信セグメント・キューに置くことができる。TCP実装はユーザからのこれ以上のSENDを受け付けず、FIN-WAIT-1状態に入る。この状態ではRECEIVEが許可される。FINの前及びFINを含むすべてのセグメントは、確認応答されるまで再送される。もう一方のTCPピアがFINを確認し、自身のFINを送信すると、最初のTCPピアはこのFINにACKを返すことができる。FINを受信するTCPエンドポイントはACKを返すが、ユーザがコネクションをCLOSEDするまで、自身の FINを送信しないことに留意すること。
ケース2: TCPエンドポイントがネットワークからFINを受信する
ネットワークから未承諾のFINが到着した場合、受信側のTCPエンドポイントはそれに対してACKを返し、コネクションを終了することをユーザに伝えることができる。ユーザはCLOSEで応答し。これにより、TCPエンドポイントは残りのデータを送信した後、もう一方のTCPピアにFINを送信することができる。次に、TCPエンドポイントは、自身のFINが確認されるまで待機し、その後コネクションを削除する。ACKが来なかった場合、ユーザ・タイムアウト後にコネクションが中断され、ユーザにその旨が通知される。
ケース3: 両方のユーザが同時に閉じる
コネクションの両端のユーザが同時にCLOSEすると、FINセグメントが交換される(図13)。FINに先行するすべてのセグメントが処理され、確認応答されると、各TCPピアは受信したFINに対してACKを送信できる。どちらも、これらのACKを受信すると、コネクションを削除する。
図12: 通常のクローズ・シーケンス
図13: 同時クローズ・シーケンス
TCPコネクションは2つの方法で終了することができる: (1) FINハンドシェイクを使用した通常のTCPクローズ・シーケンス(図12)、そして(2) 1つ以上のRSTセグメントが送信され、コネクションの状態が直ちに破棄される「中止」。リモート側から受信したFINまたはRSTによりローカルTCPコネクションがリモート側によって閉じられた場合、ローカル・アプリケーションはそれが通常のクローズなのか中止されたのかを通知しなければならない(MUST-12)。
3.6.1.ハーフクローズ接続
通常のTCPクローズ・シーケンスは、バッファリングされたデータを両方向に確実に配信する。TCPコネクションの2方向は独立して閉じられるため、コネクションが「半分閉じる」、つまり片向だけ閉じられる可能性があり、ホストはハーフクローズされたコネクション上でオープン方向にデータを送信し続けることができる。
ホストは、「半二重」のTCPクローズ・シーケンスを実装してもよいが、そうすることで、CLOSEを呼び出したアプリケーションはコネクションのデータの読み取り続行ができなくなる(MAY-1)。そのようなホストが、受信データがまだTCPコネクションで保留されている間にCLOSEコールを発行した場合、またはCLOSEが呼び出された後に新しいデータを受信した場合、そのTCP実装はデータが失われたことを示すためにRSTを送信する必要がある(SHLD-3)。この議論については、[23]のセクション2.17を参照のこと。
コネクションがアクティブに閉じられた場合、2xMSL(最大セグメント生存時間)の時間、TIME-WAIT状態に留まらなければならない(MUST-13)。しかし、TIME-WAIT状態から直接コネクションを再開するため、リモートTCPエンドポイントからの新しいSYNを受け入れてもよい(MAY-2)。
- 新しいコネクションの初期シーケンス番号を、前のコネクションで使用した最大のシーケンス番号よりも大きく割り当てる。
- SYNが古い重複であることが判明した場合、TIME-WAIT状態に戻る。
TCPタイムスタンプ・オプションが利用可能な場合、より高いコネクション確立レートをサポートするために、改良されたアルゴリズムが[40]で説明されている。TIME-WAITを削減するこのアルゴリズムは、タイムスタンプ・オプションが一般的に使用され、TIME-WAITを削減するために使用することで、ビジーなインターネット・サーバに利点をもたらすため、実装すべきベスト・カレント・プラクティスである(SHLD-4)。
3.7. セグメンテーション(セグメント化)
「セグメンテーション」という用語は、TCPが送信側アプリケーションからバイトストリームを取り込み、そのバイトストリームをTCPセグメントにパケット化するときに実行するアクティビティを指す。個々のTCPセグメントは、アプリケーションからの個々の送信(またはソケット書き込み)コールに1対1で対応しないことが多い。アプリケーションは上位層プロトコルのメッセージの粒度で書き込みを行うことができるが、TCPは送受信されるTCPセグメントの境界と、ユーザ・アプリケーション・データの読み取りまたは書き込みバッファの境界との相関関係を保証しない。ダイレクト・データ・プレースメント(DDP)やMarker PDU Aligned Framing (MPA)[34]を使用するリモート・ダイレクト・メモリ・アクセス(RDMA)のような特定のプロトコルでは、TCPセグメントとアプリケーション・データ・ユニットの関係を制御できる場合にパフォーマンスの最適化が可能である。MPAには、TCPセグメントとアプリケーション・メッセージ・データ構造間のこの関係を検出して検証するためのメカニズムが含まれているが、これはRDMAのようなアプリケーションに特有のものである。一般に、複数の目標が、TCP実装によって作成されるTCPセグメントのサイズに影響する。
大きなセグメントの送信を推進する目的には以下のようなものがある:
- ネットワーク内に飛行中のパケット数を減らす。
- 少ない数の割り込みとレイヤ間相互作用を可能にすることで、処理効率と潜在的なパフォーマンスを向上させる。
- TCPヘッダのオーバーヘッドを制限する。
大きなセグメントを送信することによるパフォーマンス上の利点は、サイズが大きくなるにつれて、減少する可能性があり、メリットが逆転する境界が存在する可能性があることに留意する。例えば、いくつかの実装アーキテクチャでは、セグメント内の1025バイトは、純粋にコピー操作のデータ・アラインメントが原因で、1024バイトよりもパフォーマンスが低下する可能性がある。
小さなセグメントの送信を促進する目的は以下のようなものがある:
- パケット損失やパケットの断片化が発生するため、IPネットワーク・パス上の最小MTUよりも大きなIPデータグラムとなるようなTCPセグメントの送信を回避する。さらに悪いことに、ファイアウォールやミドルボックスの中には、断片化されたパケットや、断片化に関連するICMPメッセージをドロップするものもある。
- アプリケーション・データ・ストリームの遅延を防止する。特に、TCPがアプリケーションのデータ生成を待機しているときや、アプリケーションがより多くのデータを生成するためにピアからのイベントや入力を待機しているときに発生する。
- TCPセグメントと下位層のデータユニット(例えば、IPの下位、IP MTUより小さいセルまたはフレームサイズのリンク)との間の「運命共有」(fate sharing)を可能にする。
これらの相反する目標を達成するために、TCPには、以下のサブセクションで説明するように、最大セグメント・サイズ・オプション、パスMTUディスカバリ、Nagleアルゴリズム、IPv6ジャンボグラムのサポートなどのメカニズムがある。
3.7.1. 最大セグメント・サイズ・オプション
TCPエンドポイントは、MSSオプションの送信と受信の両方を実装しなければならない(MUST-14)。
TCP実装は、受信MSSがIPv4のデフォルトの536またはIPv6のデフォルトの1220はと異なる場合、すべてのSYNセグメントでMSSオプションを送信すべきであり(SHLD-5)、常に送信してもよい(MAY-3)。
コネクションのセットアップ時にMSSオプションを受信しない場合、TCP実装はデフォルトの送信MSSを、 IPv4の場合は536(576 - 40)、IPv6の場合は1220(1280 - 60)を前提としなければならない(MUST-15)。
TCPエンドポイントが実際に送信するセグメントの最大サイズである「有効な送信MSS」は、送信MSS(リモートホストで利用可能な再構築バッファサイズ、EMTU_R [19])とIP層が許可する最大送信サイズ(EMTU_S [19])の小さい方にしなければならない(MUST-16):
Eff.snd.MSS = min(SendMSS+20, MMS_S) - TCPhdrsize - IPoptionsize
ここで
- SendMSSは、リモートホストから受信したMSS値、またはMSSオプションを受信していない場合は、IPv4の場合はデフォルトの536、IPv6の場合は1220になる。
- MMS_Sは、TCPが送信できるトランスポート層メッセージの最大サイズである。
- TCPhdrsizeは、TCPヘッダの固定部とオプションのサイズ。これは、オプションが存在しない(まれな)場合には20だが、TCPオプションを送信する場合には、もっと大きくなる可能性がある。いくつかのオプションがセグメントに含まれないかもしれないが、送信されるセグメントごとに、送信者はそれに応じてデータ長をEff.snd.MSS内で調整する必要があることに留意する。
- IPoptionsizeは、TCPコネクションに関連付けられたIPv4オプションまたはIPv6拡張ヘッダのサイズ。一部のオプションや拡張ヘッダがパケットに含まれないかもしれないが、送信されるセグメントごとに、送信者はそれに応じてデータ長をEff.snd.MSS内で調整する必要があることに留意する。
MSSオプションで送信されるMSS値は、有効なMTUからIPヘッダ固定部とTCPヘッダを差し引いた値に等しくなければならない。MSSオプションの値を計算するときにIPオプションとTCPオプションの両方を無視することで、パケットで送信されるIPオプションやTCPオプションがある場合、送信者はそれに応じてTCPデータのサイズを小さくしなければならない。RFC 6691 [43]では、この点について詳しく説明している。
MSSオプションで送信するMSS値は、以下の値でなければならない:
MMS_R - 20
ここで、MMS_Rは、受信(及びIP層で再構築)可能なトランスポート層メッセージの最大サイズである(MUST-67)。TCPはIP層からMMS_RとMMS_Sを取得する。RFC 1122のセクション3.4の汎用呼び出しGET_MAXSIZESを参照のこと。これらは、IP MTUに相当するEMTU_RとEMTU_S[19]で定義されている。
IPヘッダまたはTCPヘッダのいずれかが固定でない状況でTCPが使用される場合、送信者は、IP及びTCPオプションが使用するオクテット数だけ、パケット内のTCPデータ量を削減しなければならない。これは、RFC 6691のセクション3.1で説明しているように、歴史的に混乱の原因となってきた。
3.7.2. パスMTU検出
TCP実装は、直接接続されたリンクのMTUを認識しているかもしれないが、ネットワーク・パス全体のMTUを把握していることは稀である。IPv4の場合、RFC 1122は、直接接続されていない送信先に対して、IP層のデフォルトの実効MTUを576以下にすることを推奨しており、IPv6の場合は1280になる。これらの固定値を使用すると、TCPコネクションのパフォーマンスと効率が制限される。代わりに、TCPがセグメンテーションの決定を改善するために、Path MTU Discovery (PMTUD)やPacketization Layer Path MTU Discovery (PLPMTUD)の実装を強く推奨する。PMTUDとPLPMTUDはどちらも、TCPがパス上(IPv4の場合)とソースの断片化(IPv4とIPv6)の両方を回避するセグメント・サイズを選択するのに役立つ。
IPv4[2]またはIPv6[14]のPMTUDは、TCP、IP、ICMPの中で実装する。ソースの断片化の回避とIPv4のDF(don't fragment)フラグの設定の両方に依存しており、後者はパス上の断片化を禁止している。セグメントが大きすぎてリンクを通過できない場合は、パス上のルータからのICMPエラーに依存する。PMTUDを使用したTCP実装に対するいくつかの調整が、実際に経験する問題に対処するためにRFC 2923に記載されている[27]。PLPMTUD[31]は、PMTUDの標準化過程の改良版であり、パス全体のICMPサポートの要件を緩和し、ICMPが一貫して伝達されない場合でも、ソースの断片化を回避しようとする場合のパフォーマンスを向上させる。これら4つのRFCのメカニズムはすべて、TCP実装に含めることが推奨される。
TCP MSSオプションは受信できるパケットサイズの上限を指定する([43]を参照)。したがって、MSSオプションの値の設定が小さすぎると、PMTUDまたはPLPMTUDがより大きなパスMTUを見つける能力に影響を与える可能性がある。RFC 1191は、多くの古いTCP実装が、ローカル以外の送信先のTCP MSSを接続されたインタフェースのMTUから推奨するように導出するのではなく、536(IPv4のデフォルト MTU 576バイトに相当)に設定することの意味について説明している。
3.7.3. 可変MTU値を持つインタフェース
例えば、RObust Header Compression (ROHC)[37]などの可変圧縮を使用する場合、有効MTUは変化することがある。TCP実装では、圧縮ペイロードの最も効率的な使用をサポートするため、可能な限り大きなMSSを広告したいと考える。残念なことに、圧縮方式によっては、エンドポイントのコンプレッサ/デコンプレッサの状態を再同期するために、完全なヘッダ(したがって、より小さなペイロード)を送信する必要がある。MSSオプションで広告する値を計算するために最大のMTUを使用する場合、TCPの再送がコンプレッサの再同期を妨げる可能性がある。
その結果、インタフェースの有効MTUがパケットごとに異なる場合、TCP実装はMSSオプションで広告する値を計算するために、インタフェースの最小の有効MTUを使用する必要がある(SHOULD)(SHLD-6)。
3.7.4. Nagleアルゴリズム
「Nagleアルゴリズム」はRFC 896[17]で規定され、生成される小さなパケットが多すぎるという初期の問題を軽減するためにRFC 1122[19]で推奨された。これは、現在ほとんどのTCPコードベースに実装されているが、場合によってはマイナーな変更が加えられている(付録A.3を参照)。
未確認応答のデータがある場合(つまり、SND.NXT > SND.UNA)、送信側のTCPエンドポイントは、未確認応答データが確認されるまで、またはTCPエンドポイントがフルサイズのセグメント(Eff.snd.MSSバイト)を送信できるようになるまで、(PSHビットに関係なく)すべてのユーザデータをバッファリングする。
TCP実装は、短いセグメントを結合させるNagleアルゴリズムを実装する必要がある(SHLD-7)。しかし、アプリケーションが個々のコネクションでNagleアルゴリズムを無効にする方法がなければならない(MUST-17)。どのような場合でも、データの送信はスロースタート・アルゴリズム[8]によって課せられる制限にも従う。
Nagleアルゴリズムと遅延確認応答との間には問題のある相互作用が存在する可能性があるため、いくつかの実装では、付録A.3で説明するようなNagleアルゴリズムのマイナーなバリエーションを使用している。
3.7.5. IPv6ジャンボグラム
TCPでIPv6ジャンボグラムをサポートするには、実装はMSSオプションが伝えることができる64 KBの制限を超えるTCPセグメントを送信できる必要がある。RFC 2675[24]は、65,535バイトのMSS値を無限大として扱い、実際のMSSを決定するためにPath MTU Discovery[14]を使用すると規定している。
MTUが65,575を超えるリンクへのコネクションをサポートしないIPv6ノードでは、ジャンボ・ペイロード・オプションを実装したり、理解したりする必要はなく[24]、現在のIPv6ノード要件にはジャンボグラムのサポートが含まれていない[55]。
3.8. データ通信
コネクションが確立されると、データはセグメントの交換によって通信する。エラー(チェックサム検査の失敗)やネットワークの輻輳によってセグメントが失われる可能性があるため、TCPは再送を使ってすべてのセグメントを確実に配送する。ネットワークやTCPの再送によって、重複したセグメントが届くかも知れない。シーケンス番号に関するセクション(セクション 3.4 )で説明したように、TCP実装は、セグメント内のシーケンス番号と確認応答番号に対して特定のテストを実行し、その許容性(acceptability)を検証する。
データの送信側は、次に使用するシーケンス番号を変数SND.NXTに記録する。データの受信側は、変数RCV.NXTで予期される次のシーケンス番号を記録する。データの送信側は、変数SND.UNAに最も古い未確認シーケンス番号を記録するデータフローが一時的にアイドル状態になり、送信されたデータがすべて確認された場合、3つの変数は等しくなる。
送信者がセグメントを作成して送信するとき、送信側はSND.NXTを進める。受信側がセグメントを受け入れると、RCV.NXTを進めて確認応答を送信する。データ送信者が確認応答を受信すると、SND.UNAを進める。これらの変数の値がどの程度異なるかが、通信の遅延の指標となる。変数が進める量は、セグメント内のデータとSYNまたはFINフラグの長さである。ESTABLISHED状態になったら、すべてのセグメントは現在の確認応答情報を保持する必要があることに留意する。
CLOSEユーザ呼び出しは、受信セグメントのFIN制御フラグと同様に、プッシュ機能(セクション3.9.1参照)を意味する。
3.8.1. 再送タイムアウト
インターネットワーク・システムを構成するネットワークは多様であり、TCPコネクションの用途も多岐にわたるため、再送タイムアウト(RTO)は動的に決定しなければならない。
RTOは、 RTTサンプルを取得するためのKarnのアルゴリズムを含む、[10]のアルゴリズムに従って計算しなければならない(MUST-18)。
RFC 793には、IEN 177[71]で言及された作業に基づいた、RTOを計算するための初期の手順例が含まれている。これはその後、RFC 1122に記述されたアルゴリズムに置き換えられ、RFC 2988で更新され、さらにRFC 6298で再び更新された。
RFC 1122は、再送されたパケットが元のパケットと同一である場合(これは、データ境界が変更されていないだけでなく、ヘッダも変更されていないことを意味する)、同じIPv4識別フィールドを使用してもよい(RFC 1122のセクション3.2.1.5を参照)(MAY-4)と認めている。同じIP識別フィールドは、データグラムが断片化された場合にのみ意味を持つため、再利用できる[44]。TCP実装は、いかなる方法でもこのIPv4ヘッダ・フィールドに依存したり、通常このフィールドと相互作用したりすべきではない。これは、重複した送信セグメントを示す合理的な方法でも、重複した受信セグメントを識別する合理的な方法でもない。
3.8.2. TCP輻輳制御
RFC 2914 [5]は、インターネットにおける輻輳制御の重要性を説明している。
RFC 1122は、バン・ジェイコブソンの輻輳制御アルゴリズムであるスロースタートと輻輳回避を、同じセグメントの連続するRTO値に対して指数関数的バックオフとともに実装することを要求した。RFC 2581は、スロースタートと輻輳回避に加え、高速再送と高速回復のIETF標準化過程の規定を提供した。RFC 5681は、これらのアルゴリズムの現在の記述であり、TCP輻輳制御のガイドラインを提供する現在の標準化過程の仕様である。RFC 6298は、RTO値の指数関数的なバックオフについて規定しており、新しいデータを含む後続のセグメントが送信され、再送なしで確認されるまでバックオフされた値を保持することを含む。
TCPエンドポイントは、輻輳崩壊状態の発生を回避するために、基本的な輻輳制御アルゴリズムであるスロースタート、輻輳回避、RTOの指数関数的バックオフを実装しなければならない(MUST-19)。RFC 5681とRFC 6298は、IETF標準化過程で広く適用可能な基本アルゴリズムを記述している。他にも複数の適切なアルゴリズムが複数存在し、広く使用されている。多くのTCP実装は、エンドポイントで使用するように設定できる代替アルゴリズムをサポートしている。エンドポイントは、アルゴリズムがRFC 2914、RFC 5033[7]、RFC 8961[15]に記載されているIETF標準化過程のTCP仕様に準拠している限り、そのような代替アルゴリズムを実装してもよい(MAY-18)。
明示的輻輳通知(ECN)はRFC 3168で定義され、IETF標準化過程の拡張であり、多くの利点を持つ[51]。
TCPエンドポイントは、RFC 3168に記述されているようにECNを実装すべきである(SHLD-8)。
3.8.3. TCPコネクションの失敗
TCPエンドポイントによる同じセグメントの過度の再送は、リモートホストまたはインターネットワーク・パスに何らかの障害が発生していることを示す。この障害は継続時間が短い場合もあれば長い場合もある。データセグメントの過度な再送を処理するには、以下の手順を使用しなければならない(MUST-20):
(a) 同じセグメントに対して発生した再送の量を測定する2つのしきい値R1とR2がある。R1とR2は、時間単位か再送回数として測定する(必要に応じて、現在のRTO及び対応するバックオフを変換係数として使用する)。
(b) 同じセグメントの送信回数が閾値R1以上になったら、ネガティブ・アドバイス([19]のセクション3.3.1.4を参照)をIP層に渡し、デッド・ゲートウェイの診断をトリガーとする。
(c) 同じセグメントの送信回数が閾値R1より大きい閾値R2に達すると、コネクションを閉じる。
(d) アプリケーションは特定のコネクションに対してR2の値を設定できなければならない(MUST-21)。例えば、双方向アプリケーションはR2を「無限大」に設定し、切断のタイミングをユーザが制御できるようにする場合がある。
(e) TCP実装は、R1に到達してR2の前に、配信の問題をアプリケーションに通知すべきである(SHLD-9) (そのような情報がアプリケーションによって無効化されていない限り、「非同期レポート」セクション(セクション3.9.1.8)を参照)。これは例えば、リモートログイン・アプリケーション・プログラムがユーザに通知することを可能にする。
R1の値は、現在のRTOで、少なくとも3回の再送に対応する必要がある(SHLD-10)。R2の値は少なくとも100秒に対応する必要がある(SHLD-11)。
TCPコネクションをオープンする試みは、SYNセグメントの過度な再送、またはRSTセグメントやICMP Port Unreachableの受信によって失敗する可能性がある。SYNの再送は、アプリケーション層への通知を含め、データ再送について説明した一般的な方法で処理しなければならない。
しかし、R1とR2の値は、SYNセグメントとデータセグメントで異なるかも知れない。特に、SYNセグメントのR2は、少なくとも3 分間はセグメントを再送できるように十分な大きさに設定しなければならない(MUST-23)。もちろん、アプリケーションはもっと早くコネクションを閉じる(つまり、オープン試行を断念する)こともできる。
3.8.4. TCPのキープアライブ
TCPコネクションが「アイドル」と言われるのは、ある程度の期間にわたって受信セグメントが受信されず、送信する新しいデータまたは未確認応答のデータがない場合をいう。
実装者はTCP実装に「キープアライブ」を含めてもよいが(MAY-5)、この慣行は広く受け入れられているわけではない。しかし、いくつかのTCP実装はキープアライブ・メカニズムを含んでいる。アイドル状態のコネクションがまだアクティブであることを確認するために、これらの実装はTCPピアから応答を引き出すように設計したプローブ・セグメントを送信する。このようなセグメントには通常、SEG.SEQ = SND.NXT-1が含まれ、1オクテットのガベージデータが含まれる場合もあれば、含まれない場合もある。キープアライブが含まれる場合、アプリケーションはTCPコネクションごとにキープアライブをオンまたはオフにできなければならず(MUST-24)、デフォルトでオフに設定しなければならない(MUST-25)。
キープアライブ・パケットは、未処理の送信データがなく、一定期間内にそのコネクションに対してデータも確認応答パケットも受信していない場合にのみ送信しなければならない(MUST-26)。この間隔は設定可能でなければならず(MUST-27)、デフォルトは2時間以上でなければならない(MUST-28)。
データを含まないACKセグメントはTCPによって確実に送信されないことを覚えておくことが非常に重要である。その結果、キープアライブのメカニズムが実装されている場合、特定のプローブに対する応答の失敗をデッド・コネクションと解釈してはならない(MUST-29)。
実装は、データを含まないキープアライブ・セグメントを送信する必要がある(SHLD-12)。しかし、誤ったTCP実装との互換性を保つために、1つのガベージ・オクテットを含むキープアライブ・セグメントを送信するように設定してもよい(MAY-6)。
3.8.5. 緊急情報の通信
実装の違いやミドルボックスとの相互作用の結果、新しいアプリケーションはTCPの緊急メカニズムを採用すべきではない(SHLD-13)。しかし、TCP実装は依然として緊急メカニズムのサポートをしなければならない(MUST-30)。いくつかのTCP実装が緊急ポインタをどのように解釈するかについての情報は、RFC 6093 [39]に記載されている。
TCP緊急メカニズムの目的は、送信側ユーザが受信側ユーザに緊急データを受け入れるよう促すことと、受信側TCPエンドポイントが、現在わかっているすべての緊急データがユーザが受信したことを受信側ユーザに通知できるようにすることである。
このメカニズムにより、データストリームのあるポイントを緊急情報の終端として指定することができる。このポイントが受信側TCPエンドポイントの受信シーケンス番号(RCV.NXT)よりも先である場合、TCP実装はユーザに「緊急モード」に移行するように指示しなければならない。受信シーケンス番号が緊急ポインタに追いつくと、TCP実装はユーザに「通常モード」に移行するように指示しなければならない。ユーザが「緊急モード」になっている間に緊急ポインタが更新された場合、その更新はユーザには見えない。
この方法は、送信するすべてのセグメントで伝送される緊急フィールドを用いる。URG制御フラグは、緊急フィールドに意味があり、緊急ポインタを得るためにセグメント・シーケンス番号に追加しなければならないことを示す。このフラグがない場合は、未処理の緊急データがないことを示す。
緊急指示を送信するには、ユーザは少なくとも1つのデータ・オクテットも送信しなければならない。送信ユーザもプッシュを指示すると、送信先プロセスへの緊急情報のタイムリーな配信が強化される。緊急ポインタの変更は送信アプリケーションによって書き込まれるデータに対応するため、シーケンス領域内で緊急ポインタが「後退」することはできないが、TCP受信側は無効な緊急ポインタ値に対して堅牢でなければならないことに留意する。
TCP実装は、任意の長さの緊急データ・シーケンスをサポートしなければならない(MUST-31)[19]。
緊急ポインタは、緊急データに続くオクテットのシーケンス番号を指さなければならない(MUST-62)。
TCP実装は、緊急ポインタを受信し、それ以前に保留中の緊急データがなかった場合、または緊急ポインタがデータストリーム内で進むたびに、アプリケーション層に非同期で通知しなければならない(MUST-32)。TCP実装は、アプリケーションがコネクションから読み取るべき緊急データがどれだけ残っているかを知る方法、または少なくともさらに読み取るべき緊急データが残っているかどうかを判断する方法を提供しなければならない(MUST-33)。
3.8.6. ウィンドウの管理
各セグメントで送信されるウィンドウは、ウィンドウの送信者(データ受信者)が現在受け入れる準備ができているシーケンス番号の範囲を示す。これは、このコネクションで現在利用可能なデータ・バッファ領域に関連しているという前提がある。
送信側TCPエンドポイントは、送信されるデータを現在のウィンドウに適合するセグメントにパッケージ化し、再送キューにあるセグメントを再パッケージ化してもよい。このような再パッケージ化は必須ではないが、役立つ場合がある。
一方向のデータフローを持つコネクションでは、ウィンドウ情報はすべて同じシーケンス番号を持つ確認応答セグメントで伝送されるため、順序が崩れて到着した場合に順序を変更する方法はない。これは深刻な問題ではないが、ウィンドウ情報が時々データ受信側からの古いレポートに基づいてしまうことがある。この問題を回避するための改善策は、最も高い確認応答番号を伝送するセグメント(つまり、以前に受信した最大の確認応答番号以上の確認応答番号を持つセグメント)のウィンドウ情報に影響を与える。
大きなウィンドウを示すことで、送信を促す。受け入れられる量を超えるデータが到着した場合、そのデータは破棄される。その結果、過度な再送が発生し、ネットワークと TCPエンドポイントの負荷が不必要に増加する。小さなウィンドウを指定すると、新しいセグメントを送信するたびに往復遅延が発生する程度にデータの送信が制限される可能性がある。
提供されるメカニズムは、TCPエンドポイントが大きなウィンドウを広告し、その後、それほど多くのデータを受け入れずに、はるかに小さなウィンドウを広告することができる。このいわゆる「ウィンドウの縮小」は強く推奨しない。堅牢性の原則[19]は、TCPピアが自らウィンドウを縮小することはないが、他のTCPピアがそのような動作をした場合に備えることを規定している。
TCP受信者はウィンドウを縮小してはならない。つまり、ウィンドウの右端は左に移動する必要がある(SHLD-14)。ただし、送信側TCPピアは、「使用可能なウィンドウ」(セクション3.8.6.2.1を参照)が負になる可能性のあるウィンドウ縮小に対して堅牢でなければならない(MUST-34)。
これが発生した場合、送信者は新しいデータを送信してはならない(SHLD-15)が、通常はSND.UNAとSND.UNA+SND.WNDの間の古い未確認応答データを再送する必要がある(SHLD-16)。送信者は、SND.UNA+SND.WNDを超えて古いデータを再送してもよい(MAY-7)が、ウィンドウの右端を超えるデータが確認応答されなかった場合、コネクションをタイムアウトしてはならない(SHLD-17)。ウィンドウがゼロに縮小した場合、TCP実装は標準的な方法(後述)でウィンドウをプローブしなければならない(MUST-35)。
3.8.6.1. ゼロ・ウィンドウ・プロービング
送信側TCPピアは、ウィンドウを「プローブ」するために、少なくとも1オクテットの新しいデータを定期的に送信するか(利用可能な場合)、送信ウィンドウがゼロであっても受信側TCPピアに再送しなければならない。この再送は、どちらかのTCPピアのウィンドウがゼロになったときに、ウィンドウの再開がもう一方のピアに確実に報告されることを保証するために不可欠である。これは他の文書ではゼロ・ウィンドウ・プロービング(ZWP)と呼ばれている。
(提供された)ゼロ・ウィンドウ・プロービングをサポートしなければならない(MUST-36)。
TCP実装は、提供された受信ウィンドウを無期限に閉じたままにしてもよい(MAY-8)。受信側TCPピアがプローブ・セグメントに応答して確認応答を送信し続ける限り、送信側TCPピアはコネクションをオープンにしたままにすることを許可しなければならない(MUST-37)。これにより、[19]のセクション4.2.2.17に記載している「プリンタの用紙切れ」状況などのシナリオでTCPが機能できるようになる。この動作は、[41]で説明しているように、実装のリソース管理に関する懸念に左右される。
受信側TCPピアにゼロ・ウィンドウが存在し、セグメントが到着した場合でも、そのピアは次に予想されるシーケンス番号と現在のウィンドウ(ゼロ)を示す確認応答を送信しなければならない。
送信ホストは、再送タイムアウト期間の間ゼロ・ウィンドウが存在する場合、最初のゼロ・ウィンドウ・プローブを送信する必要があり(セクション 3.8.1 )(SHLD-29)、連続するプローブの間隔を指数関数的に長くする必要がある(SHLD-30)。
3.8.6.2. 愚かなウィンドウ・シンドロームの回避
「愚かなウィンドウ・シンドローム」(SWS)とは、ウィンドウを小刻みに動かす安定したパターンであり、その結果、TCPのパフォーマンスが極端に低下する。SWSを回避するアルゴリズムを送信側と受信側の両方について以下に説明する。RFC 1122には、SWS問題に関するより詳細な説明が含まれている。Nagleアルゴリズムと送信側SWS回避アルゴリズムは、パフォーマンスの向上において相補的な役割を果たすことに留意する。Nagleアルゴリズムは、送信されるデータが小刻みに増加する場合、小さなセグメントを送信することを抑制し、SWS回避アルゴリズムは、ウィンドウの右端が小刻みに増加することによって生じる小さなセグメントの送信を抑制する。
3.8.6.2.1. 送信者のアルゴリズム ─ いつデータを送信するか
TCP実装は、送信側にSWS回避アルゴリズムを含まなければならない(MUST-38)。
セクション3.7.4のNagleアルゴリズムは、短いセグメントを結合する方法についても説明している。
送信者は受信者の合計バッファ領域(RCV.BUFF)を(直接的には)知らないので、送信側のSWS回避アルゴリズムは受信側のものよりも難しい。うまく機能することが分かっているアプローチは、送信側がMax(SND.WND)(接続上でこれまでに確認した最大の送信ウィンドウ)を計算し、この値をRCV.BUFFの推定値として使用する。残念ながら、これは推定値に過ぎない。受信側はいつでもRCV.BUFFのサイズを小さくすることができる。結果として生じるデッドロックを回避するには、SWS回避アルゴリズムをオーバーライドし、データの送信を強制するタイムアウトを設ける必要がある。実際には、このタイムアウトが発生することはほとんどない。
「使用可能なウィンドウ」は以下のとおり:
U = SND.UNA + SND.WND - SND.NXT
つまり、提供されたウィンドウから、送信されたが確認応答されていないデータ量を差し引いたものとなる。Dが、送信側TCPエンドポイントでキューイングされているが、まだ送信されていないデータの量である場合、以下のルールが推奨される。
データを送信する:
(1) 最大サイズのセグメントを送信できる場合、つまり以下の場合:
min(D,U) >= Eff.snd.MSS;
(2) または、データがプッシュされ、キューイングされたすべてのデータをすぐに送信できる場合、つまり以下の場合:
[SND.NXT = SND.UNA and] PUSHed and D <= U
(角括弧で囲まれた条件はNagleアルゴリズムによって課される)。
(3) または、最大ウィンドウの少なくとも一部Fsを送信できる場合、つまり以下の場合:
[SND.NXT = SND.UNA and]
min(D,U) >= Fs * Max(SND.WND);
(4) またはオーバーライド・タイムアウトが発生した場合。
ここで、Fsは分数であり、推奨値は1/2である。オーバーライド・タイムアウトは0.1~1.0秒の範囲内とする。このタイマーをゼロ・ウィンドウのプローブに使用するタイマーと組み合わせると便利な場合がある(セクション3.8.6.1)。
3.8.6.2.2. 受信者のアルゴリズム ─ いつウィンドウ・アップデートを送信するか
TCP実装は、受信側にSWS回避アルゴリズムを含まなければならない(MUST-39)。
受信側のSWS回避アルゴリズムは、正しいウィンドウ端をいつ進めるかを決定する。これは慣例的に「ウィンドウの更新」として知られている。このアルゴリズムは、遅延ACKアルゴリズム(セクション3.8.6.3)と組み合わさって、現在のウィンドウを含むACKセグメントがいつ実際に受信者に送られるかを決定する。
受信側SWSの解決策は、ネットワークからデータを小さなセグメントで受信する場合でも、正しいウィンドウ端RCV.NXT+RCV.WNDを小刻みに進めないようにすることである。
受信バッファの合計領域がRCV.BUFFとする。いつでも、この合計のうちのRCV.USERオクテットは、受信されて確認されたものの、ユーザ・プロセスがまだ消費していないデータと結びついている可能性がある。コネクションが休止(quiescent)のときは、RCV.WND = RCV.BUFFとRCV.USER = 0になる。
データが到着し、確認応答されるときに正しいウィンドウ端を固定に保つには、受信側がその全バッファ領域よりも小さいスペースを提供する必要がある。つまり、受信側、RCV.NXTが増加しても、RCV.NXT+RCV.WNDを一定に保つRCV.WNDを指定しなければならない。したがって、総バッファ領域RCV.BUFFは通常、次の3つの部分に分割される:
受信側に提案されたSWS回避アルゴリズムは、RCV.NXT+RCV.WNDの縮小が満足するまで固定し続けることである。
RCV.BUFF - RCV.USER - RCV.WND >= min( Fr * RCV.BUFF, Eff.snd.MSS )
ここで、Frは推奨値が分数1/2であり、Eff.snd.MSSはコネクションに対する有効な送信MSSである(セクション3.7.1を参照)。不等式が満たされると、RCV.WNDがRCV.BUFF-RCV.USERに設定される。
このアルゴリズムの一般的な効果は、Eff.snd.MSSの増分でRCV.WNDを進めることであることに留意する(現実的な受信バッファ: Eff.snd.MSS < RCV.BUFF/2)。また、受信側はそれ自身のEff.snd.MSSを使用しなければならず、それが送信側のものと同じであると仮定しなければならないことも留意する。
3.8.6.3. 遅延確認応答 ─ いつACKセグメントを送信するか
TCPデータ・セグメントのストリームを受信しているホストは、受信したデータ・セグメントごとにACK(確認応答)セグメントを送信するのではなく、いくつかのセグメントをまとめてACKを送信することで、ネットワークとホストの両方の効率を向上させることができる。これは「遅延ACK」として知られている。
TCPエンドポイントは遅延ACKを実装する必要がある(SHLD-18)が、ACKは過度に遅延すべきではない。特に、遅延は0.5秒未満でなければならない(MUST-40)。ACKは、少なくとも2つ目のフルサイズのセグメント、または新しいデータの2*RMSSバイトごとに生成する必要がある(RMSSは、確認応答されるセグメントを受信するTCPエンドポイントによって指定されたMSS、または指定されていない場合はデフォルト値)(SHLD-19)。ACKの過度の遅延は、ラウンドトリップ・タイミングとパケットの「クロッキング」アルゴリズムを妨害する可能性がある。遅延ACKの動作についてのより詳細な議論は、 RFC 5681[8]のセクション4.2にある。これには、損失回復を早めるために、順番が入れ替わったセグメント、シーケンス領域のギャップ以上のセグメント、またはギャップのすべてまたは一部を埋めるセグメントを直ちに確認するための推奨事項が含まれている。
汎用受信オフロード(GRO)[72]、ACK圧縮、ACKデシメーション[28]など、ACKの数をさらに削減するいくつかの現在の慣行があることに留意する。
3.9. インタフェース
もちろん、懸念されるインタフェースは2つある: ユーザ/TCPインタフェースとTCP/下位レベルのインタフェースである。ユーザ/TCPインタフェースについては、かなり精巧なモデルがあるが、下位レベルのプロトコル・モジュールへのインタフェースは、下位レベルのプロトコルの仕様によって詳細に規定されるため、ここでは未定義のままとする。下位レベルがIPの場合、TCP実装が使用する可能性のあるパラメータ値のいくつかを紹介する。
3.9.1. ユーザ/TCPインタフェース
TCP実装に対するユーザコマンドについて、以下の機能説明は、オペレーティング・システムごとに機能が異なるため、どう見ても架空のものである。その結果、TCP実装が異なれば、ユーザ・インタフェースも異なる可能性があることを読者に警告しなければならない。しかし、すべてのTCP実装が同じプロトコル階層をサポートできることを保証するには、すべてのTCP実装が最低限のサービスセットを提供しなければならない。このセクションでは、すべてのTCP実装に必要な機能インタフェースを規定する。
また、[53]のセクション3.1では、TCPが提供するプリミティブを特定しており、実装者向けの追加リファレンスとして使用できる。
以下のセクションでは、ユーザ/TCPインタフェースの機能的特徴を説明する。使用している表記法は、高水準言語のほとんどのプロシージャまたは関数呼び出しに似ているが、この使用法はトラップタイプのサービス呼び出しを除外することを意図していない。
以下で説明するユーザコマンドは、プロセス間通信をサポートするためにTCP実装が実行しなければならない基本機能を規定する。個々の実装は、独自の正確な書式を定義する必要があり、基本関数の組み合わせやサブセットを単一の呼び出しで提供できる。特に、実装によっては、指定されたコネクションに対してユーザが最初のSENDまたはRECEIVEを発行したときに、自動的にコネクションをOPENすることを望むかも知れない。
プロセス間通信機能を提供する場合、TCP実装はコマンドを受け入れるだけでなく、サービスを提供するプロセスに情報を返さなければならない。後者は以下で構成される:
(a) コネクションに関する一般的な情報(例えば、割り込み、リモートクローズ、不特定のリモート・ソケットのバインディング)。
(b) 成功またはさまざまな種類の失敗を示す、特定のユーザ・コマンドに対する応答。
3.9.1.1. Open
フォーマット: OPEN (ローカルポート, リモートソケット, アクティブ/パッシブ [, タイムアウト] [, Diffserv フィールド] [, セキュリティ/コンパートメント] [, ローカル IPアドレス] [, オプション] -> ローカル・コネクション名
アクティブ/パッシブ・フラグがパッシブに設定されている場合、これは着信接続に対するLISTENコールである。パッシブOPENは、特定の接続を待機するために完全に指定されたリモートソケットか、任意の呼び出しを待機する指定されていないリモートソケットのいずれかを持つことができる。完全に指定されたパッシブな呼び出しは、その後のSENDの実行によってアクティブにすることができる。
トランスミッション・コントロール・ブロック(TCB)が作成され、OPENコマンド・パラメータのデータが部分的に埋められる。
パッシブOPENコールはすべて、LISTEN状態で新しいコネクション・レコードを作成するか、エラーを返す。以前に作成されたコネクション・レコードに影響を与えてはならない(MUST-41)。
複数の同時コネクションをサポートするTCP実装は、同じローカルポートを持つコネクション・ブロックがSYN-SENTまたはSYN-RECEIVED状態にある間に、アプリケーションがポートでLISTENできるようにするOPENコールを提供しなければならない(MUST-42)。
アクティブなOPENコマンドでは、TCPエンドポイントはすぐにコネクションを同期(つまり確立)する手順を開始する。
タイムアウトがある場合、呼び出し元はTCPに送信されるすべてのデータ対してタイムアウトを設定することができる。タイムアウト時間内にデータが送信先に正常に送信されなかった場合、TCPエンドポイントはコネクションを中止する。現在のグローバル・デフォルトは5分である。
TCP実装またはオペレーティング・システムの何らかのコンポーネントは、指定されたDiffservフィールド値またはセキュリティ/コンパートメントを持つコネクションを開くユーザの権限を検証する。OPEN呼えび出しにDiffservフィールド値またはセキュリティ/コンパートメントの指定がない場合、デフォルト値を使用しなければならないことを示す。
TCPは、セキュリティ/コンパートメント情報がOPENコールで要求されたものとまったく同じ場合にのみ、受信リクエストを一致するものとして受け入れる。
ユーザが指定したDiffservフィールド値は、送信パケットにのみ影響し、ネットワークを経由する途中で変更される可能性があり、受信パケットとは直接関係ない。
ローカルのコネクション名は、TCP実装によってユーザに返される。ローカルなコネクション名は、<ローカルソケット、リモートソケット>のペアで定義されるコネクションの短縮用語として使用できる。
オプションの「ローカルIPアドレス」パラメータをサポートし、ローカルIPアドレスの指定できるようにしなければならない(MUST-43)。これにより、マルチホーミング時に使用するローカルIPアドレスを選択する必要があるアプリケーションを実現する。
「ローカルIPアドレス」パラメータを指定したパッシブOPENコールは、そのアドレスへのコネクション要求の受信を待つ。パラメータが指定されていない場合、パッシブOPENはローカルIPアドレスへの受信接続要求を待ち、その後、使用される特定のアドレスにコネクションのローカルIPアドレスをバインドする。
アクティブなOPENコールの場合、指定された「ローカルIPアドレス」パラメータがコネクションを開くために使用される。パラメータが指定されていない場合、ホストは適切なローカルIPアドレスを選択する(RFC 1122のセクション3.3.4.2を参照)。
マルチホームホスト上のアプリケーションが、アクティブにTCPコネクションを開くときにローカルIPアドレスを指定しない場合、TCP実装は(最初の)SYNを送信する前にIP層にローカルIPアドレスを選択するように要求しなければならない(MUST-44)。RFC 1122のセクション3.4の関数GET_SRCADDR()
を参照のこと。
それ以外の場合はすべて、このコネクション上で前のセグメントが送受信しており、TCP実装はそれらの以前のセグメントで使用されたものと同じローカルアドレスを使用しなければならない(MUST-45)。
TCP実装は、無効なリモートIPアドレス(ブロードキャストまたはマルチキャスト・アドレス)に対するローカルOPENコールをエラーとして拒否しなければならない(MUST-46)。
3.9.1.2. Send
フォーマット: SEND(ローカルコネクション名, バッファアドレス, バイト数, URGENTフラグ[,PUSHフラグ] [,タイムアウト])
この呼び出しは、指定されたユーザバッファに含まれるデータを、指定されたコネクション上で送信する。コネクションがオープンしていない場合、SENDはエラーとみなされる。実装によっては、ユーザが最初にSENDすることができる。その場合、自動的に OPENを実行する。例えば、これはアプリケーション・データをSYNセグメントに含める1つの方法かも知れない。呼び出し元プロセスがこのコネクションの使用を許可していない場合、エラーを返す。
TCPエンドポイントはSENDコールにPUSHフラグを実装してもよい(MAY-15)。PUSHフラグが実装されていない場合、送信側TCPピアは、(1)データを無期限にバッファリングしてはならない(MUST-60)、(2)最後にバッファリングされたセグメント(つまり、送信キューにデータがなくなったとき)にPSHビットを設定しなければならない(MUST-61)。以下の残りの説明は、PUSHフラグがSENDコールでサポートしていることを前提としている。
PUSHフラグが設定されている場合、アプリケーションはデータを速やかに受信者に送信することを意図しており、バッファから作成された最後のTCPセグメントにPSH ビットを設定する。
PSHビットはレコードマーカーではなく、セグメント境界には依存しない。送信者は、データをパケット化する際、連続するビットを折り畳んで、可能な限り大きなセグメントを送信する必要がある(SHLD-27)。
PUSHフラグが設定されていない場合、送信効率を上げるために、そのデータは後続のSENDからのデータと結合してもよい。アプリケーションがPUSHフラグを設定せずに一連のSENDコールを発行した場合、TCP実装はデータを送信せずに内部的に集約してもよい(MAY-16)。Nagleアルゴリズムが使用されている場合、TCP実装はPUSHフラグに関係なく、送信前にデータをバッファするかも知れないことに留意する(セクション3.7.4を参照)。
アプリケーション・プログラムは、通信のデッドロックを回避するためにデータを強制的に配信する必要がある場合は常に、SENDコールでPUSHフラグを設定することが論理的に要求される。しかし、TCP実装は、パフォーマンスを向上させるために、可能な限り最大サイズのセグメントを送信する必要がある(SHLD-28)(セクション3.8.6.2.1を参照)。
実装の違いとミドルボックスの問題のため、新しいアプリケーションはURGENTフラグ[39]を設定すべきではない(SHLD-13)。
URGENTフラグが設定されている場合、送信先TCPピアに送信されるセグメントには緊急ポインタを設定する。緊急ポインタが、その緊急ポインタに先行するデータが受信側プロセスで消費されていないことを示す場合、受信側TCPピアは受信プロセスに緊急状態を通知する。URGENTフラグの目的は、緊急データの処理するように受信を促し、現在わかっている緊急データをすべて受信したときに受信側に示すことにある。送信側ユーザのTCP実装が緊急信号を送信する回数は、受信側ユーザに緊急データの存在を通知する回数と必ずしも一致しない。
OPENでリモートソケットを指定していないが、コネクションが確立されている場合(例えば、ローカルソケットにリモートセグメントが到着したために、LISTENのコネクションが特定された場合)、指定されたバッファが暗黙のリモートソケットに送信される。未指定のリモートソケットでOPENを使用するユーザは、リモートソケットのアドレスを明示的に知らなくてもSENDを使用できる。
しかし、リモートソケットが指定される前にSENDを試みると、エラーが返される。ユーザはSTATUSコールでコネクションの状態を確認することができる。TCPの実装によって、未指定のソケットがバインドされるとユーザに通知することがある。
タイムアウトが指定された場合、このコネクションの現在のユーザタイムアウトは新しいタイムアウトに変更される。
最も単純な実装では、SENDは送信が完了するかタイムアウトを超えるまで、送信プロセスに制御を返さない。しかし、この単純な方法はデッドロックが発生する可能性があり(例えば、コネクションの両側がRECEIVEを行う前にSENDを試行する可能性がある)、パフォーマンスが低下するため、推奨しない。より洗練された実装では、プロセスがネットワークI/Oと同時に実行され、さらに、複数のSENDが進行中であることを許可するようにすぐに戻る。複数のSENDは先着順で処理されるため、TCPエンドポイントはすぐに処理できないSENDをキューに入れる。
私たちは、暗黙のうちに非同期ユーザ・インタフェースを想定しており、SENDが後で何らかのSIGNALまたは疑似割り込みをTCPエンドポイントから引き出す。別の選択肢は、応答を即座に返すことである。例えば、SENDは送信されたセグメントが遠くのTCPエンドポイントに確認されていなくても、即座にローカルな確認応答を返すかも知れない。最終的に成功すると楽観的に考えることもできる。間違っていた場合、タイムアウトにより、コネクションは閉じられてしまう。この種の(同期)実装では、いくつかの非同期シグナルが存在するが、これはコネクション自体に対処するものであり、特定のセグメントやバッファに対処するものではない。
プロセスがさまざまなSENDのエラーまたは成功表示を区別するために、SENDリクエストに対するコード化された応答と共にバッファアドレスを返すことが適切かも知れない。TCPからユーザへの通知について、呼び出し側のプロセスに返すべき情報を示して、以下で説明する。
3.9.1.3. Receive
フォーマット: RECEIVE(ローカル・コネクション名, バッファアドレス, バイト数) -> バイト数, URGENTフラグ[, PUSHフラグ]
このコマンドは、指定されたコネクションに関連付けられた受信バッファを割り当てる。このコマンドの前にOPENがないか、呼び出し元のプロセスがこのコネクションの使用が許可されていない場合、エラーが返される。
最も単純な実装では、バッファが一杯になるか何らかのエラーが発生するまで、制御は呼び出し側プログラムに戻らないが、この方式はデッドロックの影響を受けやすい。より洗練された実装では、一度に複数のRECEIVEを発行することができる。これらのバッファは、セグメントが到着するたびに満たされる。この戦略は、PUSHが検出されたか、バッファが一杯になったことを呼び出し側プログラムに通知するために、より複雑なスキーム(おそらく非同期)を必要とするが、スループットを向上させることができる。
TCP受信者は、受信したPSHビットをインタフェースのPUSHフラグを介してアプリケーション層に渡してもよい(MAY-17)が、必須ではない(これはRFC 1122のセクション4.2.2.2で明確化された)。以下のRECEIVEコールを説明する残りの文章は、PUSH指示の渡すことがサポートされていることを前提としている。
PUSHが検出される前にバッファを埋めるのに十分なデータが到着した場合、RECEIVEに対する応答ではPUSHフラグは設定されない。バッファは保持できる限りのデータが詰め込まれる。バッファが一杯になる前にPUSHが検出された場合、バッファは部分的に一杯になった状態で返され、PUSHが示される。
緊急のデータがある場合は、TCPからユーザへの信号が到着次第、ユーザに通知される。したがって、受信側のユーザは「緊急モード」になっている必要がある。URGENTフラグがオンの場合、追加の緊急データが残っている。URGENTフラグがオフの場合、このRECEIVEの呼び出しはすべての緊急データが返されているため、ユーザは「緊急モード」を終了することができる。緊急ポインターに続くデータ(緊急ではないデータ)は、ユーザに境界が明確にマークされてない限り、先行する緊急データと同じバッファでユーザに配信されないことに留意すること。
複数の未処理のRECEIVEを区別し、バッファが完全に埋まっていない場合に対処するために、リターンコードにはバッファポインタと受信したデータの実際の長さを示すバイトカウントの両方が含まれる。
RECEIVEの別の実装として、TCPエンドポイントがバッファ・ストレージを割り当てたり、TCPエンドポイントがリングバッファをユーザと共有することがある。
3.9.1.4. Close
フォーマット: CLOSE (ローカル接続名)
このコマンドは、指定されたコネクションを閉じる。コネクションがオープンしていないか、呼び出し元のプロセスがこのコネクションの使用が許可されていない場合、エラーが返される。コネクションを閉じることは、フロー制御が許す限り、未処理のSENDがすべて処理されるまで送信する(そして再送する)という意味で、正常な操作であることを意図している。したがって、複数のSENDコールを行い、その後にCLOSEを呼び出し、すべてのデータが送信先に送信されることを期待することは受け入れられるべきである。また、リモートピアは最後のデータを送信しようとしているかも知れないため、ユーザはCLOSINGコネクション上でRECEIVEを続けるべきであることも明確にすべきである。したがって、CLOSEは「もう送信する必要はない」という意味であって、「もう受信しない」という意味ではない。(ユーザレベルのプロトコルがよく考えられていない場合)、終了側がタイムアウトになる前にすべてのデータを削除できないことが発生する可能性がある。このイベントでは、CLOSEはABORTに変わり、終了するTCPピアは諦める。
ユーザは、自分の意思で、またはTCP実装からのさまざまなプロンプト(例えば、リモート終了が実行された、送信タイムアウトを超過した、送信先にアクセスできない)に応答して、いつでもコネクションを閉じることができる。
コネクションを閉じるにはリモートTCPピアとの通信が必要なため、コネクションは短時間閉じた状態のままになることがある。TCPピアがCLOSEコマンドに応答する前にコネクションを再開しようとすると、エラー応答が返される。
クローズはプッシュ機能も意味する。
3.9.1.5. Status
フォーマット: STATUS(ローカル接続名) -> ステータスデータ
これは実装に依存するユーザコマンドであり、悪影響を及ぼさずに除外することができる。返される情報は通常、コネクションに関連付けられたTCBから取得する。
このコマンドは、以下の情報を含むデータブロックを返す:
ローカルソケット、
リモートソケット、
ローカル接続名、
受信ウィンドウ、
送信ウィンドウ、
接続状態、
確認応答を待っているバッファの数、
受信保留中のバッファの数、
緊急状態、
Diffservフィールド値、
セキュリティ/コンパートメント、及び
送信タイムアウト。
コネクションの状態や実装自体によっては、この情報の一部が利用できなかったり、意味をなさないことがある。呼び出し元のプロセスがこのコネクションを使用する権限を持たない場合、エラーが返される。これは、許可されていないプロセスがコネクションに関する情報を取得することを防ぐ。
3.9.1.6. Abort
フォーマット: ABORT(ローカル接続名)
このコマンドを実行すると、保留中のすべてのSENDとRECEIVESを中止し、TCBを削除し、特別なRSTメッセージをそのコネクションのリモートTCPピアに送信する。実装によっては、未処理のSENDまたはRECEIVEごとに、ユーザは中止指示を受け取るかも知れないし、単にABORT確認応答を受け取るかも知れない。
3.9.1.7. Flush
TCP実装の中には、FLUSHコールが含まれているものがある。これは、ユーザがSENDコールを発行したものの、現在の送信ウィンドウの右側に残っているデータのTCP送信キューを空にする。つまり、シーケンス番号の同期を失うことなく、キューに入れられた送信データを可能な限りフラッシュする。FLUSHコールは実装してもよい(MAY-14)。
3.9.1.8. 非同期レポート
ソフトTCPエラー状態をアプリケーションに報告するメカニズムがなければならない(MUST-47) 。一般的に、トランスポート層から非同期に呼び出される、アプリケーションが提供するERROR_REPORTルーチンの形式を取ると想定する:
ERROR_REPORT(ローカルなコネクション名, 理由, 補足理由)
理由と補足理由パラメータの正確なフォーマットはここでは規定しない。ただし、アプリケーションに非同期で報告される条件には、以下が含まれなければならない。
- ICMPエラーメッセージの到着(いくつかのメッセージ・タイプはアプリケーションへのレポート生成を抑制する必要があるため、各ICMPメッセージ・タイプの処理に関する説明についてはセクション3.9.2.2を参照)
- 過剰な再送(セクション3.8.3を参照)
- 緊急ポインタ前進(セクション3.8.5を参照)
しかし、そのようなERROR_REPORTコールを受け取りたくないアプリケーション・プログラムは、これらの呼び出しを効果的に無効にできる必要がある(SHLD-20)。
3.9.1.9. DiffServフィールドの設定(IPv4 TOSまたはIPv6トラフィック・クラス)
アプリケーション層は、コネクション上で送信されるセグメントに対して、DiffServフィールドを指定できなければならない(MUST-48)。DiffServフィールドには6ビットのDifferentiated Services Codepoint (DSCP)値が含まれる。これは必須ではないが、アプリケーションはコネクションの有効期間中にDiffServフィールドを変更できる必要があるSHLD-21)。TCP実装は、コネクション上でセグメントを送信するときに、IP層に現在のDiffServフィールド値を変更せずに渡す必要がある(SHLD-22)。
DiffServフィールドはコネクションの各方向で独立して指定されるため、受信側アプリケーションはACKセグメントで使用されるDiffServフィールドを指定する。
TCP実装は、直近に受信したDiffServフィールドをアプリケーションに渡してもよい(MAY-9)。
3.9.2. TCP/下位レベルのインタフェース
TCPエンドポイントは、下位レベルのプロトコル・モジュールを呼び出して、ネットワーク上で実際に情報を送受信する。TCPの下位層にある現在の2つの標準インターネット・プロトコル(IP)バージョンは、IPv4[1]とIPv6[13]である。
下位レベルのプロトコルがIPv4の場合、サービスの種類(DiffServフィールド内で使用される)と存続期間の引数を提供する。TCPはこれらのパラメータに以下の設定を使用する:
Diffservフィールド: DiffservフィールドのIPヘッダ値はユーザが指定する。これには、Diffservコードポイント(DSCP)のビットが含まれる。
生存時間(TTL): TCPセグメントの送信に使用されるTTL値は設定可能でなければならない(MUST-49)。
- RFC 793ではTTLの定数として1分(60秒)を指定したのは、想定される最大セグメントの最大存続期間が2分だったからであることに留意する。これは、インターネット・システムが1分以内にセグメントを配送できない場合、セグメントを破棄することを明示的に要求することを意図していた。RFC 1122はRFC 793を更新し、TTLを設定可能にすることを要求した。
- Diffservフィールドは接続中に変更できることに留意する(RFC 1122のセクション4.2.4.2)。しかし、アプリケーション・インタフェースはこの機能をサポートしていない可能性があり、アプリケーションは個々のTCPセグメントに関する情報を持っていないため、せいぜい粗い粒度でしか変更できない。この制限は、RFC 7657(セクション 5.1、5.3、6)でさらに説明している[50]。一般的に、アプリケーションは接続中にDiffservフィールド値を変更すべきではない(SHLD-23)。
どのような下位レベルのプロトコルも、IPと機能的に等価なサービスを提供するためと、TCPチェックサムで使用するために、送信元アドレス、送信先アドレス、プロトコル・フィールド、及び「TCP長」を決定する何らかの方法を提供しなければならない。
受信したオプションが IP層からTCPに渡されるとき、TCP実装は理解できないオプションを無視しなければならない(MUST-50)。
TCP実装はタイムスタンプ(MAY-10)とレコード経路(MAY-11)オプションをサポートしてもよい。
3.9.2.1. ソース・ルーティング
下位レベルがIP(またはこの機能を提供する他のプロトコル)で、ソース・ルーティングが使用される場合、インタフェースは経路情報の通信を許可しなければならない。これは、TCPチェックサムで使用する送信元アドレスと送信先アドレスが、発信元と最終的な送信先となるようにするために特に重要である。また、コネクション要求に応答するために戻り経路を保存することも重要である。
アプリケーションは、TCPコネクションをアクティブにオープンするときにソース経路を指定できなければならず(MUST-51)、これはデータグラムで受信したソース経路よりも優先されなければならない(MUST-52)。
TCPコネクションがパッシブにオープンされ、(戻り経路を含む)IPソース経路オプションが完了したパケットが到着した場合、TCP実装は戻り経路を保存し、このコネクションで送信されるすべてのセグメントにそれを使用しなければならない(MUST-53)。異なるソース経路が後のセグメントに到着した場合、後の定義が前の定義をオーバーライドする必要がある(SHLD-24)。
3.9.2.2. ICMPメッセージ
TCP実装は、IP層から渡されたICMPエラーメッセージに基づいて動作し、エラーを発生させたコネクションにメッセージを送信しなければならない(MUST-54)。必要な多重分離情報は、ICMPメッセージに含まれるIPヘッダに含まれる。
これは、IPv4 ICMPに加えてICMPv6にも適用される。
[35]には、異なる応答を返す可能性がある「ソフト」エラーまたは「ハード」エラーに分類されるICMPとICMPv6メッセージに関する説明が含まれている。ICMPメッセージのクラスに対する処理を以下で説明する。
送出抑制(Source Quench)
TCP実装は、受信したICMP送出抑制メッセージを静かに破棄しなければならない(MUST-55)。説明については[11]を参照のこと。
ソフトエラー
IPv4 ICMPの場合、以下が含まれる: 送信先到達不可能 ─ コード0、1、5; 時間切れ ─ コード 0、1; 及びパラメータ異常。
ICMPv6の場合、以下が含まれる: 送信先到達不可能 ─ コード 0、3; 時間切れ ─ コード 0、1; パラメータ異常 ─ コード0、1、2。
これらの到達不可能メッセージはソフトエラー状態を示すため、TCP実装はコネクションを中止してはならない(MUST-56)。アプリケーションはその情報を利用できるようにする必要がある(SHLD-25)。
ハードエラー
ICMPでは、送信先到達不可能 ─ コード2~4が含まれる。
これらはハードエラー状態なので、TCP実装はコネクションを中止する必要がある(SHLD-26)。[35]では、同期状態のいずれかのコネクションでICMPハードエラーを受信した場合にコネクションを中止しない実装もあることに留意する。
[35]のセクション4では、コネクション確立中にソフトエラーをハードエラーとして扱う実装の動作が広く普及していることに留意すること。
3.9.2.3. 送信元アドレスの検証
RFC 1122は、着信SYNパケットでアドレスを検証することを要求している:
無効な送信元アドレスを持つ着信SYNは、TCPまたはIP層のいずれかが無視しなければならない(MUST-63)(セクション3.2.1.3を参照)。
TCP実装は、ブロードキャスト・アドレスまたはマルチキャスト・アドレスにアドレス指定された着信SYNセグメントを静かに破棄しなければならない(MUST-57)。
これにより、コネクション状態と応答が誤って生成されることが防止され、実装者は、RFC 1122で具体的に示されているように、このガイダンスがSYNだけでなく、すべての着信セグメントに適用されることに留意する必要がある。
(続く)
更新履歴
- 2024.6.11
- 2024.6.22
Discussion