TCP(Transmission Control Protocol)

データを送り届けることについて信頼性を求めるアプリケーションで使用。TCPはアプリケーションデータを送信する前に、「TCPコネクション」という論理的な通信路を作って通信環境を整える。TCPコネクションは、データをやり取りするそれぞれの端末から見て、送信専用に使用する「送信パイプ」と受信専用に使用する「受信パイプ」で構成される。2本の論理的なパイプを使用して「送りまーす!」「受け取りました!」と確認しあいながらデータを送るため、信頼性が向上する。

図:TCPは確認しあいながらデータを送る

IPヘッダーのプロトコル番号は「6」。上位層(アプリケーション層)から受け取ったデータを「TCPペイロード」とし、「TCPヘッダー」をくっつけることによって「TCPセグメント」にする。

TCPのパケットフォーマット

信頼性を高めるため、少々複雑。たくさんあるフィールドを活用して、どの「送ります。」に対する「受け取りました。」なのかを確認したり、パケットの送受信量を調整したりしている。

図:TCPのパケットフォーマット

送信元/宛先ポート番号

UDPと同じで、アプリケーションの識別に使用される。

シーケンス番号

TCPセグメントを正しい順序に並べるために使用される4バイトのフィールド

送信側の端末は、アプリケーションから受け取ったデータの各バイトに対して、「初期シーケンス番号(ISN,Initial Sequence Number)」から順に、通し番号を付与する。受信側の端末は、受け取ったTCPセグメントのシーケンス番号を確認して、番号順に並べ替えたうえでアプリケーションに渡す。

図:送信側の端末が通し番号(シーケンス番号)を付与

シーケンス番号は3ウェイハンドシェイクをするときにランダムな値が初期シーケンス番号としてセットされ、TCPセグメントを送信するたびに送信したバイト数分だけ加算されていく。そして4バイトで管理できるデータ量(232=4Gバイト)を超えたら、再び「0」に戻ってカウントアップする。

図:シーケンス番号はTCPセグメントを送信するたびに送信したバイト数分だけ加算される

確認応答番号

ACK番号、Acknowledge番号

「次はここからのデータをください」と相手に伝えるために使用される

コントロールビットのACKフラグが「1」になっている時だけ有効になるフィールドで、具体的には「受け取り切ったデータのシーケンス番号(最後のシーケンス番号)+1」、つまり「シーケンス番号+アプリケーションデータの長さ」がセットされている。あまり深く考えずに、クライアントがサーバーに「次にこのシーケンス番号以降のデータをくださーい」と言っているようなイメージで捉えるとわかりやすい。

TCPは、シーケンス番号と確認応答番号を協調的に動作させることによってデータの信頼性を確保している。

図:確認応答番号(ACK番号)

データオフセット

TCPヘッダーの長さを表す

端末はこの値を見ることによって、どこまでがTCPヘッダーであるか知ることができる。IPヘッダーと同じく、4バイト単位に換算した値が入る。

コントロールビット

コネクションの状態を制御するフィールド

TCPはコネクションを作るとき、これらのフラグを「0」にしたり「1」にしたりすることによって、現在コネクションがどのような状態にあるかを伝えあっている。

ビットフラグ名説明概要
1ビット目CWRCongestion Window ReducedENC-Echoに従って、輻輳ウィンドウを減少させたことを通知するフラグ
2ビット目ECEECN-Echo輻輳が発生していることを明示的に通信相手に通知するフラグ
3ビット目URGUrgent Pointer field significant緊急を表すフラグ
4ビット目ACKAcknowledment field significant確認応答を表すフラグ
5ビット目PSHPush Function速やかにアプリケーションにデータを渡すフラグ
6ビット目RSTReset the connectionコネクションを強制切断するフラグ
7ビット目SYNSynchronize sequence numbersコネクションをオープンするフラグ
8ビット目FINNo more data from senderコネクションをクローズするフラグ

ウィンドウサイズ

受け取れるデータサイズを通知する

どんなに高性能な端末でも、一気に、かつ無尽蔵にパケットを受け取れるわけではない。そこで「これくらいまでだったら受け取れますよ」という感じで、確認応答を待たずに受け取れるデータサイズをウィンドウサイズとして通知する。

「0」がもう受け取れないことを表す。送信側の端末は、ウィンドウサイズが「0」のパケットを受け取ると、いったん送信するのを止める。 

チェックサム

受け取ったTCPセグメントが壊れていないか、整合性のチェックに使用される

TCPのチェックサム検証にも「1の補数演算」が採用されている

緊急ポインタ

コントロールビットのURGフラグが「1」になっているときだけ有効

緊急データがあったときに、緊急データを示す最後のバイトのシーケンス番号がセットされる。

オプション

TCPに関する拡張機能を通知しあうために使用される

「種別(Kind)」によって定義されるいくつかのオプションを、「オプションリスト」として並べていく形で構成される。オプションリストの組み合わせはOSやそのバージョンによって異なる。特に重要なオプションが「MSS(Maximum Segment Size)」と「SACK(Selective Ackowledgment)」。

種別オプションヘッダーRFC意味
0End Of Option ListRFC9293オプションリストの最後であることを表す
1NOP(No-Operatin )RFC9293何もしない。オプションの区切り文字として使用する
2MSS(Maximum Segment Size)RFC9293アプリケーションデータの最大サイズを通知する
3Window ScaleRFC7323ウィンドウサイズの最大サイズ(65535バイト)を拡張する
4SACK(Selective ACK) PermittedRFC2018Selective ACK(選択的確認応答)に対応している
5SACK(Selective ACK)RFC2018Selective ACKに対応しているときに、すでに受信したシーケンス番号を通知する
8TimestampsRFC7323パケットの往復時間(RTT) を計測するタイムスタンプに対応している
30MPTCP(Multipath TCP)RFC86884Multipath TCPに対応している
34TCP Fast OpenRFC7413RCP Fast Opneに対応していることを通知したり、Cookieの情報を渡す

MSS

Maximum Segment Size

TCPペイロードの最大サイズ

同じくMの付く3文字の用語で混同しがちなMTU(Maximum Transmission Unit)と比較しながら説明する。

MTUは、IPパケットの最大サイズを表す。大きなアプリケーションデータは小分けにしてちょこちょこ送信する。そのとき最も大きい小分けの単位がMTU。MTUは伝送媒体によって異なっていて、例えばイーサネットの場合、デフォルトで1500バイト。

それに対してMSSは、TCPセグメントに詰め込むことができるアプリケーションデータの最大サイズを表す。MSSは、明示的に設定したり、VPN環境だったりしないかぎり、「MTU-40バイト(IPv4ヘッダー+TCPヘッダー)」となる。たとえばイーサネット(レイヤー2)+IPv4(レイヤー3)環境の場合、デフォルトのMTUが1500バイトなので、Mssは1460(=1500-40)バイトになる。トランスポート層はアプリケーションデータをMSSに区切ってTCPにカプセル化する。

TCP端末は3ウェイハンドシェイクをするときに、「このMSSのアプリケーションデータだったら受け取れますよー」とサポートしているMSSをお互いに教えあう。

図:MSSとMTU

SACK

Selective Acknowledge

消失したTCPセグメントだけを再送する機能

ほぼすべてのOSでサポートされている

TCPは「アプリケーションデータをどこまで受け取ったか」を確認応答番号(ACK番号)のみで判断している。そのため、部分的にTCPセグメントが消失した場合、消失したTcpセグメント以降のすべてのTCPセグメントを再送してしまうと効率的でない。SACKは「どこからどこまで受け取ったか」という範囲をオプションフィールドで通知する。

図:SACK(Selective Ackowledgment)

TCPにおける状態遷移

図:TCPコネクションの状態遷移

接続開始フェーズ

TCPコネクションは「3ウェイハンドシェイク」でコネクションをオープンするところから始まる。

3ウェイハンドシェイクとは、コネクションを確立する前に行うあいさつを表す処理手順のこと。

クライアントとサーバーは、3ウェイハンドシェイクの中で、お互いがサポートしている機能やシーケンス番号を決めて、「オープン」と呼ばれる下準備を行う。コネクションを作りにいく側(クライアント)の処理を「アクティブオープン」、コネクションを受け付ける側(サーバー)の処理を「パッシブオープン」という。

  1. 3ウェイハンドシェイクを開始する前、クライアントは「CLOSED」、サーバーは「LISTEN」の状態。CLOSEDはコネクションが完全に閉じている状態、つまり何もしていない状態。LISTENはクライアントからのコネクションを待ち受けている状態。
  2. クライアントはSYNフラグを「1」、シーケンス番号にランダムな値をセットしたSYNパケットを送信し、オープンの処理に入る。この処理によってクライアントは「SYN-SENT」状態に移行し、続くSYN/ACKパケットを待つ。
  3. SYNパケットを受け取ったサーバーはパッシブオープンの処理に入る。SYNフラグとACKフラグを「1」にセットしたSYN/ACKパケットを返し、「SYN-RECEIVED」状態に移行する。なお、この時のシーケンス番号はランダム、確認応答番号はSYNパケットのシーケンス番号に「1」を足した値になる。
  4. SYN/ACKパケットを受け取ったクライアントは、ACKフラグを「1」にセットしたACKパケットを返し、「ESTABLISHED」状態に移行する。ESTABLISHEDはコネクションが確立した状態。この状態になって、初めて実際のアプリケーションデータを送受信できるようになる。
  5. ACKパケットを受け取ったサーバーはESTABLISHED状態に移行する。この状態になって、初めて実際のアプリケーションデータを送受信できるようになる。これまでのシーケンス番号と確認応答番号のやりとりによって、アプリケーションデータの最初に付与されるシーケンス番号がそれぞれ確定する。

図:3ウェイハンドシェイク

接続確立フェーズ

3ウェイハンドシェイクが完了したら、実際のアプリケーションデータのやり取りが始まる。TCPは、アプリケーションデータ転送の信頼性を保つため、「フロー制御」「輻輳制御」「再送制御」という3つの制御をうまく組み合わせて転送を行っている。

フロー制御

受信側の端末が行う流量調整。受信側の端末はウィンドウサイズのフィールドを使用して、自分が受け取ることができるデータ量を通知している。送信側の端末はウィンドウサイズまでは確認応答(ACK) を待たずにどんどんTCPセグメントを送るが、それ以上のデータは送らない。これにより受信側の端末が受け取り切れるように考慮しつつ、可能な限りたくさんのデータを送信するようにしている。この一連の動作を「スライディングウィンドウ」という。

輻輳制御

送信側の端末が行う流量制御。「輻輳」とは、ざっくり言うとネットワークにおける混雑のこと。TCPは「輻輳制御アルゴリズム」によってパケットの送信数を制御している。このパケットの送信数のことを「輻輳ウィンドウ(cwnd, congestion window)」という。

図:輻輳制御のイメージ

輻輳制御にはどのような情報をもとに輻輳状態を判断するかによっていくつかの種類がある。現在の主流はパケットロス(パケットの消失)をもとに輻輳状態を判断する「CUBIC」。CUBICはパケットロスを検知したら輻輳が発生していると判断して輻輳ウィンドウを減らす。パケットロスを検出しなくなったら輻輳が解消していると判断して、輻輳ウィンドウを増やす。

再送制御

パケットロスが発生したときに行うパケットの再送機能。TCPはACKパケットによってパケットロスを検知し、パケットを再送する。再送制御が発動するタイミングは受信側の端末がきっかけで行われる「重複ACK(Duplicate ACK)」と送信側の端末がきっかけで行われる「送信タイムアウト(Retransmission Time Out、RTO)」の2つ。

重複ACK

受信側の端末は、受け取ったTCPセグメントのシーケンス番号が飛び飛びになるとパケットロスが発生したと判断して確認応答が同じACKパケットを連続して送出する。このACKパケットのことを「重複ACK(Dupulicate ACK)」という。

送信側の端末は、一定回数以上の重複 ACKを受け取ると、対象となるTCPセグメントを再送する。(SACKが有効な場合はタイムアウトが発生したTCPセグメントのみ、無効な場合はタイムアウトが発生したTCPセグメント以降をすべて再送する。)重複ACKをトリガーとする再送制御のことを「Fast Retransmit(高速再送)」という。

再送タイムアウト

送信側の端末は、TCPセグメントを送信した後、ACK(確認応答)パケットを待つまでの時間を「再送タイマー(Retransmission Timer)」として保持している。この再送タイマーは短すぎず長すぎず、RTT(Round Trip Time、パケットの往復遅延時間)から数学的なロジックに基づいて算出される。ざっくりいうとRTTが短いほど再送タイマーも短くなる。再送タイマーはACKパケットを受け取るとリセットされる。 

接続終了フェーズ

アプリケーションデータのやり取りが終わったら、「クローズ」と呼ばれるTCPコネクションの終了処理に入る。クローズに失敗すると、不要なコネクションが端末にたまり続けてリソースを圧迫しかねないので、オープン処理よりもしっかり、かつ慎重に進めるようにできている。クライアントとサーバーは終了処理の中でFINパケットやRSTパケットを交換しあい、コネクションをクローズする。FINフラグは「もう送信するアプリけーしんデータはありません」を意味するフラグで、上位アプリケーションの挙動に合わせた形で付与される。RSTフラグはコネクションの強制切断を意味するフラグで、TCP的に予期しないエラーが発生するなどして、コネクションをすぐに切断したい時などに付与される。

TCPコネクションのオープンは必ずクライアントのSYNから始まる。それに対してクローズはクライアント、サーバーどちらのFINから始まると明確に定義されているわけではない。先にFINを送出してTCPコネクションを終わらせに行く側の処理を「アクティブクローズ」、それを受け付ける側の処理のことを「パッシブクローズ」という。処理は若干複雑だが、TCP的な処理を行う「OS」と、アプリケーション的な処理を行う「アプリケーション」の連携を注視しながら順々に追っていくとわかりやすい

4ウェイハンドシェイクパターン

もっとも基本的な処理。以下はクライアント側でアクティブクローズする例。

  1. クライアントは予定したアプリケーションデータをやり取りし終わると、クライアントOSに対してクローズ要求を行う。クライアントOSはこの要求に応じてアクティブクローズの処理を開始する。FINフラグをACKフラグを「1」にしたFIN/ACKパケットを送信し、サーバーからFIN/ACKパケットを待つ「FIN-WAIT-1」状態に移行する。
  2. FIN/ACKパケットを受け取ったサーバーOSは、パッシブクローズ処理を開始する。サーバーアプリケーションにクローズ処理の依頼をかけ、FIN/ACKパケットに対するACKパケットを送信する。あわせて、サーバーアプリケーション空のクローズ要求を待つ「CLOSE-WAIT」状態に移行する。
  3. ACKパケットを受け取ったクライアントOSは、サーバーからのFIN/ACKパケットを待つ「FIN-WAIT-2」状態に移行する。
  4. サーバーOSはサーバーアプリケーションからクローズ処理の要求があると、FIN/ACKパケットを送信し、自身が送信したFIN/ACKパケットに対するACKパケット、つまりクローズ処理における最後のACKパケットを待つ「LAST-ACK」状態に移行する。
  5. サーバーOSからFIN/ACKを受け取ったクライアントOSは、それに対するACKパケットを送信し、「TIME-WAIT」状態に移行する。TIME-WAITは、もしかしたら遅れて届くかもしれないACKパケットを待つ、保険のような状態。
  6. ACKパケットを受け取ったサーバーOSは「CLOSED」状態に移行し、TCPコネクションを削除する。併せてこのTCPコネクションのために確保していたリソースを開放する。これでパッシブクローズ処理は終了。
  7. TIME-WAIT状態に移行しているクライアントOSは、設定された時間を待って「CLOSED」状態に移行し、コネクションを削除、リソースを開放する。これでアクティブクローズ処理は終了。

図:4ウェイハンドシェイクによるクローズ処理

3ウェイハンドシェイクパターン

パr支部クローズ側のアプリケーションが即座にクローズ処理したときに起きうる3ウェイハンドシェイクパターン。

  1. 最初は4ウェイと同じ。クライアントOSは、クライアントアプリケーションからクローズ要求が入るとアクティブクローズ処理を開始し、FIN/ACKパケットを送信する。また、あわせてFIN-WAIT-1状態に移行する。
  2. FIN/ACKパケットを受け取ったサーバーOSは、パッシブクローズの処理を開始し、サーバーアプリケーションにクローズ処理の依頼をかける。クローズ処理の依頼を受け取ったサーバーアプリケーションは即座に処理を行い、サーバーOSがACKを返すよりも前にサーバーOSにクローズ処理を要求する。クローズ処理を受け取ったサーバーOSは、FIN/ACKパケットを送信し、それに対するACKパケットをまつ「LAST-ACK」状態に移行する。このFIN/ACKパケットは、4ウェイにおける2のACKパケットと、3のFIN/ACKパケットをまとめたものと考えてよい。
  3. サーバーからFIN/ACKを受け取ったクライアントOSは、それに対するACKパケットを送信し、TIME-WAIT状態に移行する。
  4. ここからはまた4ウェイと同じ。

図:3ウェイハンドシェイクによるクローズ処理


Comments

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です