🤝

詳細図解 TCP

に公開
5

はじめに

TCPには多くの機能が存在します。

  1. コネクションの確立、切断
  2. 再送制御
  3. 順序制御
  4. ウィンドウ制御(フロー制御、輻輳制御)

などなど

上記の機能は知っているけどどういう仕組みで動いているか
説明できる方は少ないんじゃないでしょうか?

そこで一通り、TCPの主要機能をこちらの記事で紹介します。

おすすめの読者

  1. 周りより一歩ネットワークに詳しくなりたい人
  2. ネットワークスペシャリストを受験予定でTCPの機能をおさらいしたい人
  3. TCPの挙動をパケットキャプチャなどで追う必要がある技術者
  4. TCPの特定の機能の概要が知りたい人

上記に当てはまる方は是非読んでみてください!

1. TCPの概要

1.1. TCPの誕生

TCPは最初期のコンピューターネットワークであるARPANETの中でうまれます。
ARPANETは米国の大学と研究所から始まった「パケット通信の研究用ネットワーク」です。
ARPANET初期はNCP (Network Control Program)というプロトコルで通信が行われていましたが、その後継としてTCPが登場します。
最初の仕様は1974年に RFC675 で公開され、1981年によく知られる RFC793 として標準化されます。


図:ARPANET最初の4拠点、TCPが生まれる1981年には世界中に広がっています。

参考
第1回インターネットの先駆け、ARPANETの始まり -JPNIC
RFC 675 - Specification of Internet Transmission Control Program
RFC 793 - Transmission Control Protocol

1.2. OSI参照モデル、RFC1122(TCPモデル)におけるTCP


まずOSI参照モデル(左)TCPモデル(中) における TCP の立ち位置を確認します。

TCP はいづれのモデルでも 「トランスポート層」 で動作します。
下位のプロトコルは IP で、IPのプロトコル番号6番がTCPです。

TCP の上位では HTTP、TLS、FTP など多くのプロトコルが動作します。

つまりTCPは 送受信時以下の処理をします。
送信時:HTTPなどからデータを受け取りセグメンテーションを行い、IPにTCPセグメントを渡す。
受信時:IP から TCPセグメント を受け取り順序整列や再送などを行ったうえで、HTTPなどに TCPセグメントのペイロード を渡す。

参考
OSI参照モデル - Wikipedia
RFC 1122 - Requirements for Internet Hosts - Communication Layers

1.3. TCPの機能

TCPの代表的な機能を確認します。TCPでは以下の機能を用いて通信の信頼性を高めます。

1. 再送制御
宛先にパケットが届かなかったときに、パケットの再送を行います。
2. 順序制御
パケットの到着順番が前後した際に整列してから上位プロトコルにわたします。
3. ウィンドウ制御
受信側やネットワーク機器のキャパシティ以上のパケットを送らない用に送信側で送信パケット量を調整する機能です。さらに以下に細分化できます。
 3.1. フロー制御
 セッションごとの受信側のキャパシティを送信側に通知し、
 受信キャパシティ以上にパケットを送信しないようにします。
 3.2. 輻輳制御
 ネットワークの帯域を推定し、ネットワークの帯域以上にパケットを送信しないようにします。

詳細はそれぞれ別途解説していきます。

1.4. TCPヘッダ

上記はTCPヘッダです(※RFC9293RFC3540に準拠して作成しています)
オプション無しの場合20バイトの長さになっています。
TCPヘッダの中にはTCP通信に使われる大切な情報が沢山詰まっています。

  • セッション管理に利用する 「ポート番号」
  • 再送制御や順序制御に利用する 「シーケンス番号」「確認応答番号」
  • フロー制御に利用する 「ウィンドウサイズ」

などなど、、

「この機能はTCPヘッダのこの部分をこうつかって実現しているんだな」 ということが理解できるようになると、TCPヘッダを見ておいしいご飯が食べられるようになります。

参考
RFC 9293 - Transmission Control Protocol (TCP)
RFC 3540 - Robust Explicit Congestion Notification (ECN) Signaling with Nonces

1.5. TCPの仕様(RFC)について

TCPはRFC793で標準化され、その後何度か変更・修正されており、現在はRFC9293が最新となっています。
※RFC(Request for Comment)はインターネット技術の標準化をしている団体のIETF(Internet Engineering Task Force)が標準化した仕様を文書化したものです。

また、TCP内部で使われる輻輳制御アルゴリズムやリトライのアルゴリズム、一部のコントロールフラグやオプションは別のRFC9293以外の別のRFCで標準化されてます。

参考
IETF | Internet Engineering Task Force
インターネット用語1分解説~IETFとは~ - JPNIC

2. TCPのポート番号とセッション管理

2.1. ポート番号によるセッション管理

TCP通信をする際はクライアントとサーバはポート番号を使って通信します。
ポート番号はヘッダの一番最初の32bitで表されます。(送信元16bit、宛先16bit)

以下にTCP通信に使われるポートを図解します。

  1. サーバは設定したポート番号で待ち受けます。
    例:22番(SSH)、80番(HTTP)など

  2. クライアントは使用していないポート番号を利用してサーバに通信をリクエストします
     ここで選ばれるポート番号はOSの設定によって異なります。
     例:Linux 32768 - 61000 等

2.2. 「送信元IPアドレス」「送信元ポート番号」「宛先IPアドレス」「宛先ポート番号」を用いた送信元の識別

TCPでは「送信元IPアドレス」「送信元ポート番号」「宛先IPアドレス」「宛先ポート番号」を用いてセッションを識別します。以下に図解します。

「送信元ポート番号」が違うと別セッションとして扱われますし、「送信元IPアドレス」が違っても別セッションとして扱われます。
これによってサーバ側は1つのポートで複数のセッションを管理することができます。

2.3. IANAが管理するウェルノウンポート、登録ポート、エフェメラルポート

TCPのポート番号が一般的にどのサービスで利用されるかはICANNの機能のひとつであるIANAが管理しています。

IANAが管理するポート番号の分類は以下三種類にわかれています。

  • ウェルノウンポート(0-1023) :SSH、DNS、HTTP、HTTPS等
  • 登録ポート(1024-49151):MySQL、RDP等
  • エフェメラルポート(49152-65535):登録なし

参考までに代表的なプロトコルを以下画像にまとめます。

登録状況は以下のページから見ることができます。
Service Name and Transport Protocol Port Number Registry (IANA)

参考
Internet Corporation for Assigned Names and Numbers (ICANN)

3. TCP 3ウェイハンドシェイク

3.1. TCP 3ウェイハンドシェイクで主に使われるTCPヘッダのフィールド

まず3ウェイハンドシェイクで主に使われるTCPヘッダを確認します。

  1. シーケンス番号
  2. 確認応答番号
  3. フラグ: ACK
  4. フラグ: SYN

上記4つのフィールドが使われます。

TCPヘッダ内の位置は以下の図の通りです。

実際は 「送信元IPアドレス」「宛先IPアドレス」「送信元ポート」「宛先ポート」 を
セッション管理 や SYN Cookie に利用しますが、ここではシンプルにするため割愛します。

3.2. TCP 3ウェイハンドシェイクによるコネクションの確立

TCPでは3ウェイハンドシェイクと呼ばれるフローで、双方向に通信が可能であることを確認してからデータ転送を行います。

下図に3ウェイハンドシェイクの流れを記載します。

1. クライアントのSYN
クライアントは ランダムな値 (0 - 4,294,967,295の間)を 「シーケンス番号」に設定し、さらに 「SYN」 のビットを 1 にしてサーバに送ります。

2. サーバのSYN/ACK
サーバは「受信した シーケンス番号」 に +1 した値を 「確認応答番号」 に設定します。さらに ランダムな値(送信側とは独立したもの)を 「シーケンス番号」 に設定し、「SYN」 と 「ACK」 のビットを 1 にしてクライアントに送ります。

3. クライアントのACK
クライアントは「受信したシーケンス番号」 に +1 した値を 「確認応答番号」 に設定します。そして「受信した確認応答番号」 の値を 「シーケンス番号」 に設定し、「ACK」 のビットを 1 にしてサーバに送ります。

これでコネクションが確立されます。

3.3. Wiresharkで3ウェイハンドシェイクのパケットを見てみる

3.2.で記載した流れをパケットキャプチャツールのWiresharkで見ると実際の3ウェイハンドシェイクの流れを追うことができます。

Wireshark では見やすいように 「シーケンス番号」、 「確認応答番号」 を(relative sequence/ack number)として0番スタートで表示してくれますが、実際の値は(raw)と記載のある値です。

SYN・ACKのフラグはinfoに表示されます。

SYNを受け取ったときにシーケンス番号に +1 した値を確認応答番号に設定していることなどが見てとれます。

参考
Wireshark · Go Deep

TCPの3ウェイハンドシェイクの流れはIPAのネットワークスペシャリストはじめ多くの資格試験にもでるので覚えておきたいですね。

4. TCPの通信の流れ

4.1. コネクション確立後のシーケンス番号と確認応答番号の流れ

TCPでは3ウェイハンドシェイク後、データのやり取りを双方向で行います。
ざっくりと通信の流れを図解すると以下のようになります。

  1. 「シーケンス番号」と「確認応答番号」は3ウェイハンドシェイク完了後のものを引きつぎます。
  2. クライアントからサーバに100バイトのデータをペイロードに入れ送信します。
  3. サーバは「受け取ったシーケンス番号」にペイロードのバイト数を足した値を「確認応答番号」に設定しACKを送信します。いい変えると「次にほしいパケットのシーケンス番号を確認応答番号に設定」しACKを送信します。
  4. サーバはクライアントに200バイト送信します。
  5. 今度はACKと同時にデータ送信も行います。クライアントは前回100バイト送ったので次のパケットでは「前回のパケットのシーケンス番号」に+100した値を設定します。さらに「受け取ったシーケンス番号」に受け取ったバイト数を足した値を「確認応答番号」に設定します。
    上記のように通信が続いていきます。

同時に双方向でデータを送りあっているので少しややこしいかもしれません。
次に受信確認の方法を片方向ずつみていきます。

4.2. 送信相手がデータを受信できたことの確認方法

4.2.1. クライアント→サーバ方向

クライアントからパケットを送ったときに「サーバがパケットを受信できたこと」をクライアントが確認するのに使う項目は以下の2つです。

  • クライアント→サーバ のパケットのシーケンス番号
  • サーバ→クライアント のパケットの確認応答番号

以下に受信確認のポイントを図解します。(バイト数やシーケンス番号は例です。)

  1. クライアントはシーケンス番号234126、100バイトのペイロードでサーバにパケットを送っています。
  2. サーバは受信したシーケンス番号(234126)とペイロードサイズ(100)から確認応答番号(234226 = 234126 + 100)を設定しACKを返します。
  3. クライアントは上がった確認応答番号を見て「サーバがパケットを受信できたこと」を確認します。

上記を繰り返し、クライアントはサーバがパケットを受信できたことを確認します。

4.2.2. サーバ→クライアント方向

反対側も同様の方法で受信確認を行います。使う項目はクライアント→サーバとは逆になります

  • サーバ→クライアント のパケットのシーケンス番号
  • クライアント→サーバ のパケットの確認応答番号

ハイライトされている「シーケンス番号」、「確認応答番号」が逆になっていることがわかります。

このようにTCPではクライアント→サーバ、サーバ→クライアントの両方それぞれ相手側にちゃんとパケットが届いているか確認を行います。

5. 4ウェイコネクションクローズ(コネクションの終了)

5.1. 4ウェイコネクションクローズに利用するTCPヘッダフィールド

まず、切断する際に利用するTCPヘッダフィールドを確認しましょう。

  1. シーケンス番号
  2. 確認応答番号
  3. フラグ: ACK
  4. フラグ: FIN

上記4つのフィールドが主に使われます。TCPヘッダにおける位置は以下の図の通りです。

コネクションの確立の際は「シーケンス番号」「確認応答番号」「ACK」に加え「SYN」を利用しましたが、切断を行う場合は「SYN」の代わりに「FIN」を利用します。

5.2. 4ウェイコネクションクローズとハーフクローズ

TCPでコネクションを切断する場合は2パケットを使い片方ずつ、送信終了の処理を行います。
これをハーフクローズといいます。

両方切断する場合2×2で合計4パケットがやりとりされます。
以下に流れを図にしています。

  1. 最初に送信を終了する側が「FIN」を立ててパケットを送信します。
  2. 「FIN」を受け取った側は「受け取ったシーケンス番号」に+1した値を「確認応答番号」に設定して「ACK」を立て応答します。これで片方の送信コネクションがクローズします。
  3. もう片方はまだ送信したいデータがある場合、送信を続けます。この段階ではまだコネクションを閉じていない側はデータ送信ができます。(ない場合は 3. はスキップ)
  4. 送りたいデータがなくなったら 「FIN」を立ててパケットを送信します。
  5. 「FIN」を受け取ったら「受け取ったシーケンス番号」に+1した値を「確認応答番号」に設定して「ACK」を立て応答します。これで双方のコネクションが閉じます。

5.2. 「フラグ:RST」によるコネクションの終了

TCPでは「RST」フラグを使いコネクションの切断や拒否が可能となっています。
TCPヘッダ上で「RST」は「SYN」と「PSH」の間にあります。

以下のような状況で「RST」を利用してコネクションの拒否・切断を行います。

  • 非公開ポートへのアクセス
  • 不整合発生時のコネクションの終了
  • コネクションが上限になっているときの拒否

以下にRSTの仕様例を図解します。

このように「RST」はコネクションの拒否や、不整合時の切断に利用できます。

6. TCPの状態遷移

ここまで、TCPの3ウェイハンドシェイクによるコネクションの確立から、双方向通信の仕組み、コネクションの切断の流れまでみてきました。
これらのTCPの機能は状態遷移モデルで表されます。

6.1.TCPの状態遷移図

TCPは以下のような状態遷移図で表されます。
TCPのクライアントとサーバはいずれかの状態で動きます。

6.2. TCPの11種類の状態

上記の図にもあるように、TCPには11種類の状態があります。
それぞれの状態と、遷移する条件は以下の通りです。

1. CLOSED

コネクションが存在しない状態

  • サーバが待ち受けを開始するとLISTEN へ遷移
  • クライアントが接続を開始すると SYN-SENT へ遷移

2. LISTEN

サーバ側がクライアントからのSYNを待っている状態

  • 受信したパケットが SYN の場合、SYN-RECEIVED に遷移

3. SYN-SENT

クライアントがサーバに対してSYNを送信し、SYN/ACKを待っている状態

  • サーバから SYN/ACK を受け取ると ACK を送りESTABLISHEDへ遷移

4. SYN-RECEIVED

クライアントからの SYN を受け取り、SYN/ACK を返したあと、クライアントからの ACK を待っている状態

  • クライアントからの ACK を受け取ると ESTABLISHED へ遷移

5. ESTABLISHED

双方向(送受信とも)にコネクションが確立されている状態

  • アプリケーションで切断を開始するとFINを送信し、FIN-WAIT-1 に遷移
  • FINを受け取るとCLOSE-WAITに遷移

6. FIN-WAIT-1

ESTABLISHED 状態で、自分からFINを送り、ACK を待っている状態

  • 相手から ACK を受け取ると FIN-WAIT-2 へ遷移
  • 相手から FIN が来た場合は CLOSING に遷移 (同時にコネクションを閉じようとしたときなど)

7. FIN-WAIT-2

相手からの FIN を待っている状態

  • 相手から FIN を受け取ると ACK を返し、TIME-WAIT に遷移

8. CLOSE-WAIT

相手から FIN を受け取り、ACK送信した状態

  • アプリケーションがクローズ処理を行うと FIN を送り LAST-ACK へ遷移
     ※この状態でアプリケーションが切断処理をしないとCLOSE-WAIT が増え続ける問題が発生する。

9. CLOSING

同時クローズ が発生したときの状態
両側がほぼ同時に FIN を送信してしまい、お互いに相手のFINを受信したがACKを返していない状態

  • 相手のACKを受信しTIME-WAIT へ遷移

10. LAST-ACK

CLOSE-WAIT状態から自分もFINを送信し、ACK を待っている状態

  • 相手から ACK を受け取ると CLOSED へ遷移する。

11. TIME-WAIT

再送などで遅れて送られるパケットを加味して、一定時間ソケットを保持する状態

  • TIME-WAIT のタイマが満了すると CLOSED に遷移する。

6.3. ハーフクローズ後の FIN-WAIT-2 と CLOSE-WAIT 間通信

状態遷移の中で難解な点のひとつはコネクション切断の箇所です。
前述したハーフクローズの動作をするため、コネクションの切断は片方向ずつ行われます。

CLOSE-WAIT とFIN-WAIT-2 間でコネクションがあるときはまだ片方向通信(CLOSE-WAIT→FIN-WAIT2方向)が可能な状態になっています。

以下は該当箇所のシーケンスです。

状態遷移図の中で該当箇所をハイライトすると以下のようになります。

6.4. 典型的な状態遷移の動画

以下のポストで、典型的な状態遷移の動きを動画にしています。
上記の各状態と遷移の動きを理解した上で見ると面白いと思います。

7. 再送制御

TCPではパケットが届かなかったときに再送を行う仕組みが導入されています。
代表的アルゴリズムとしてRetransmission Timeout (RTO) とFast Retransmit の2種類を紹介します。

7.1. Retransmission Timeout(RTO)

RTOの時間 ACKが返ってこないときに送信側で再送を行う方式です。
(RFC6298で定められており、Linuxカーネルにも採用されている方式です)

以下に図解します。

  1. 100バイトのデータを送信します。
  2. 道中でパケットロスしてしまい、相手に届きません。よって受信の確認もできません。
  3. RTO時間経過しても受信確認が取れない場合、再送を行います。

シンプルなアルゴリズムですね。
このRTO時間は可変となっており、指数関数的にリトライ時間を延ばします。

参考
RFC 6298 - Computing TCP's Retransmission Timer

7.2. Fast Retransmit

Fast Retransmitでは重複した確認応答番号のACKを3つ受けるとRTOのタイマを待たずにパケットがロスしたと判断し再送を行います。
パケットのやり取りが多い場合には、RTOより早くロスしたパケットを再送できます。

以下に図解します。

  1. 100バイトのデータを送信します。
  2. シーケンス番号200番のパケットをロスしてしまいます。
  3. シーケンス番号200番がこないので、確認応答番号が200から進めません。パケットが来るたび重複ACKを送信します。
  4. 確認応答番号200のACKが4つ送られてきます。(重複ACK3つ)
      このときロスをしたと判断して再送を行います。

参考
RFC 5681 - TCP Congestion Control

8. 順序制御

TCPではシーケンス番号をもとにデータを整列してから、上位プロトコルにデータを送ります。
これによって、再送などでパケットの順序逆転が発生した時も上位プロトコルは正しい順番でデータを受け取れます。

順序制御はその特性上、パケットロスが発生した場合、ロスしたパケットが届くまで待つことになります。
これが問題となる場合はUDP等別プロトコルを採用し、上位アプリケーション側でパケットロスをどう処理するか考えます。

9. ウィンドウ制御

9.1. ウィンドウ制御の基本 (送信ウィンドウ)

TCPでは、ネットワークや受信側のキャパシティを超えてパケットを送らないように送信できるパケットの量を制御しています。
これを送信ウィンドウ (swind)といいます。

送信ウィンドウはパケットを送ると減り、ACKを受信すると回復します。
以下に送信ウィンドウが3000で、送りたいパケットが6つ、各パケット1000バイト、合計6000バイトを送りたいケースを図解します。

  1. 送信ウィンドウが3000バイトなので3つのパケット(3000バイト)を送信します。
  2. ACKが受信できるまで送信を待ちます。
  3. ACKが2つ(2000バイト分)返ってきます。
  4. ACKが2つきたので2つ(2000バイト分)パケットを送信します。
  5. ACKが1つ(1000バイト分)帰ってきます。
  6. ACKが1つきたので1つ(1000バイト分)パケットを送信します。

このように送信ウィンドウをもとに送信するデータ量を調整する機能がウィンドウ制御です。
また、この上記の一連の流れをスライディングウィンドウと呼んだりします。

9.1.1. 送信ウィンドウは受信ウィンドウと輻輳ウィンドウで決まる

送信ウィンドウサイズ(swind)は、受信ウィンドウサイズ(rwind)と輻輳ウィンドウサイズ(cwind)の小さいほうが使われます。

それぞれ以下の形で送信側は受信ウィンドウと輻輳ウィンドウの値を得ます。

  • 受信ウィンドウ:受信側で決定されTCPヘッダで通知されます。
  • 輻輳ウィンドウ:様々なアルゴリズムで送信側が計算します。

これによってTCPでは「受信側のバッファ溢れの防止」と「ネットワーク輻輳の軽減」を可能としています。

9.2. 受信ウィンドウの値と最大値

受信ウィンドウサイズは受信側の設定値が適用されます。
通常のTCPヘッダの場合16bitのため、最大65,535バイト(※)まで指定可能です。

補足
受信ウィンドウが65,535バイトでは足りない場合、RFC1323で定められているウィンドウスケーリングオプションを利用することでより大きな受信ウィンドウサイズを設定可能です。
RFC 1323 - TCP Extensions for High Performance

9.3. 輻輳ウィンドウと輻輳制御アルゴリズム

輻輳制御はネットワークのボトルネックを超えてパケットを送ってしまわないように開発された機能です。

通信経路のネットワークの帯域と使用状況はコンピュータからは見えないので、輻輳制御アルゴリズムによって推定し輻輳ウィンドウを決定します。複数のアルゴリズムを説明するとすごく長くなってしまうので、この記事では代表的なアルゴリズムのCUBICを紹介します。

9.3.1. CUBIC

CUBICはLinux、Windowsなどの汎用OSでデフォルトとして採用されている輻輳制御アルゴリズムです。
それまでは指数関数的にウィンドウサイズが増加するTahoeや線形増加するRenoといった輻輳制御アルゴリズムが主流でしたが、回線の利用効率を高める目的でCUBICが生まれました。

CUBICでは3次関数的に輻輳ウィンドウが増加します。以下にCUBIC関数を図解します。

少しわかりにくいかもしれませんが、CUBICでは時間tの3次関数でウィンドウサイズが変化します。
輻輳ウィンドウの変化を拡大すると以下のようになります。

  1. 3次関数的にウィンドウサイズが増える
  2. 輻輳検知したタイミングで輻輳ウィンドウサイズを(1-β)倍に下げる
  3. 前回輻輳検知したウィンドウサイズに近づくと増加がゆるやかになる。
  4. ∛((𝑊_𝑚𝑎𝑥 𝛽 )/𝐶)になったタイミングで前回輻輳検知したウィンドウサイズに戻る
  5. また三次関数的に増加する

上記を繰り返しウィンドウサイズを変化させていきます。

他にもGoogleが開発しているBBRや、MITが開発したCopaなど多くの輻輳制御アルゴリズムがあります。
また、輻輳制御アルゴリズムはQUICにも流用されているので、時代がQUICに変わっても輻輳制御アルゴリズムの知識はほぼそのまま使えます。

参考
異種輻輳制御機構の競合時における性能評価:CUBIC TCP vs Copa vs BBR
Performance Evaluation of Heterogeneous Congestion Control Mechanisms :
CUBIC TCP vs Copa vs BBR

Copa: Practical Delay-Based Congestion Control for the Internet
インターネットにおけるふくそう制御アルゴリズム
RFC 9438: CUBIC for Fast and Long-Distance Networks

9.4. ECN(明示的輻輳通知)を利用した輻輳制御

9.4.1. ECN(Explicit Congestion Notification) 明示的輻輳通知とは

通常、TCP通信をしている端末からはネットワークの輻輳状態を見ることができません。
そのためTCPでは、測定したパケロスや遅延等をもとに、間接的にネットワークの輻輳を検知をしています。もし通信経路にいるルータが直接輻輳を通知できるようになれば、より正確な輻輳検知が可能になります。それを実現する仕組みが ECN (:Explicit Congestion Notification) 明示的輻輳通知 です。

9.4.2. ECNによる輻輳通知の流れ

ECNが有効な場合、IPヘッダのECNフィールドが「10」ECT(0) もしくは「01」ECT(1) となります。輻輳発生時はルータがECNフィールドを「11」CE に書き換えます。(ECNが無効なときは「00」です。)

9.4.3. TCPでECNを利用した輻輳制御を有効化

TCPの輻輳制御にECNを活用する際は ECE (ECN Echo) フラグと CWR (Congestion Window Reduced) フラグを使います。
TCPレイヤでECNを有効にする際は3ウェイハンドシェイク時に両フラグを利用します (※NSは省略)

9.4.4. TCPの輻輳制御にECNが利用される流れ

TCPの輻輳制御にECNを利用する際はIPとTCPが連携して動作します。

流れを記載します

  1. ECNが有効なときIPヘッダのECNフィールドには01または10が入ります。
  2. 輻輳時ルータがECNフィールドを11(CE)に書き換えます。
  3. ECNフィールドの11(CE)を受信します。
  4. 受信した側はTCPヘッダのECEフラグを立ててパケットを送信するようになります。
  5. ECEフラグを受信します。
  6. ECEの受信を受けて、輻輳ウィンドウサイズを下げます
  7. CWRフラグを立てて、輻輳ウィンドウサイズを下げたことを通知します。
  8. 輻輳ウィンドウの縮小を受けてECEフラグを立てなくなります。

上記のような流れで輻輳制御がされます。IPレイヤとTCPレイヤが連携するのはとても面白いですね。
RFC 3168 - The Addition of Explicit Congestion Notification (ECN) to IP

10. TCPに対する攻撃

便利なTCPですが、その機能の弱点をついた攻撃がいくつかあります。
代表的な攻撃であるSYN flood(フラッド)攻撃とRST flood(フラッド)攻撃を見てみます。

10.1. SYN flood(フラッド)攻撃

SYN flood(フラッド)攻撃は3ウェイハンドシェイクの仕組みを利用して攻撃対象のサーバに大量のSYNを送りつける攻撃です。

前提
後述するSYN Cookieを使わない場合、TCPサーバはSYNが来たときにSYN Backlogを+1します。
サーバはSyn Backlogの上限の値を設定しています。

以下にIPを偽装(スプーフィング)するタイプのSYN flood(フラッド)攻撃を図解します。

  1. 攻撃者は自分のIPを偽装(スプーフィング)しSYN要求を攻撃対象に送ります。
  2. サーバはSYNが来るたびにSYN Backlogの値を増やしやがて上限に到達します。
  3. 上限に到達した場合、サーバは新しい接続要求を拒否するようになります。
  4. サーバが返したSYN/ACKは送信元IPを偽装しているので届きません。
  5. 一般ユーザがアクセスできなくなってしまいます。

10.1.1. SYN flood攻撃は代表的なDDoS攻撃

Cloudflareが公開した資料によると、2024年第4四半期にCloudflareに来たDDoS攻撃の49%がレイヤ3,4への攻撃です。
その中の38%がSYN flood攻撃となっています。(全体の20%弱がSYN flood攻撃)

2024年第4四半期、記録的な5.6TbpsのDDoS攻撃およびグローバルなDDoSの傾向 (Cloudflareブログ)

SYN flood攻撃の防御策としてSYN Cookieがあります。
SYN Cookieを使うことで「ACKを返さないタイプのSYN flood攻撃」を防ぐことが可能です。
SYN Cookieを使うとSYN要求ではコネクションを増やさず、送信者から来たACKを検証したのちにコネクションを+1します。

以下にSYN Cookieが導入されている状態で、SYN flood攻撃が来た場合の流れを図解します

  1. 攻撃者は自分のIPを偽装(スプーフィング)しSYN要求を攻撃対象に送ります。
  2. サーバはSYNを受け取りますが、コネクションのカウントはしません。
  3. 宛先/送信元IPアドレス、ポート番号、タイムスタンプ、サーバの秘密情報等を利用したハッシュ値をシーケンス番号に設定し送信します。
  4. 攻撃者はIPを偽装しているため、パケットのシーケンス番号がわかりません。
  5. 一般ユーザがリクエストを送った場合、シーケンス番号がわかるので、通常の3ウェイハンドシェイクを行います。
  6. サーバはACKのシーケンス番号から整合性を計算し問題がなければコネクションを+1します。

このようにしてSYN Cookieを使うことでSYN flood攻撃を防ぐことが可能です。

10.2. RST flood攻撃

RST flood攻撃はRSTフラグを立てた偽装パケットを攻撃対象に送りコネクションを切断する攻撃です。
切断するためには「送信元IPアドレス」「送信元ポート番号」「宛先IPアドレス」「宛先ポート」
さらに「正常な範囲のシーケンス番号」のパケットを生成する必要があるため、情報がない状態で攻撃を成功させるのは困難です。昔のTCPの実装ではシーケンス番号が予測しやすかったため、攻撃が成功しやすかったようです。
現在でこの攻撃が成功するシナリオとしては中間者攻撃が考えられるかと思います。

以下は中間者攻撃 (Man-in-the-middle (MITM) 攻撃) の例

  1. 攻撃者が通信のパケットを盗聴し、各種情報を入手します。
  2. RSTフラグを立てた悪意のあるパケットを生成・送信します。

11. オプション

TCPのオプションについて解説します。
本記事では重要なオプションであるSACKを解説します。
他のオプションについては時間を見て追記するか、別の記事に記載しようと思います。

11.1. データオフセット(TCPヘッダ長)

データオフセットはTCPのヘッダ長を表すフィールドです。「4バイトあたり1増えます。オプション無しの場合、ヘッダ長は20バイトなので5(二進数で0101) がデータオフセットに入ります。
オプションがある場合には、データオフセットは6以上となります。

11.2. SACK (オプション番号5)

11.2.1 SACKがない場合

SACKオプションがない場合、送信側は「ロスする前に受信されたシーケンス番号」しか把握できません。
そのため、ロスの発生時に既に受信されているセグメントも再送しやすくなってしまいます。

以下にSACKオプションがないときに余分な再送が発生するときの流れを図解します。

  1. パケットを複数送信します。
  2. パケットロス(シーケンス番号200)が発生します。
  3. シーケンス番号200のパケットが届いていないので、確認応答番号は200のままです。
  4. 送信側はシーケンス番号200以降どこまで届いているかわかりません。
  5. どこまで届いているかわからないので、既に届いているシーケンス番号300,400も送ってしまいます。

11.2.2 SACKがある場合

次にSACKがある場合を図解します。

  1. パケットを複数送信します。
  2. パケットロス(シーケンス番号200)が発生します。
  3. シーケンス番号200のパケットが届いていないので、確認応答番号は200のままです。
  4. 送信側はSACKオプションで受信しているパケットの範囲を把握できます。
  5. ロスしているパケットだけ再送することが可能になります。

SACKオプションがある場合、送信側は受信側がどのセグメントを受信できているか把握できます。
これによって、余分なパケットの送信を減らすことができ、通信帯域の節約につながります。

11.2.3 SACKオプションのヘッダ

SACKオプションのヘッダは受信済のデータの範囲をシーケンス番号で示します。オプションの番号と長さと合わせて最低10バイト、複数範囲を設定する場合8バイトずつ増えます。

たとえばシーケンス番号200 (100バイト) とシーケンス番号500 (100バイト) がロスしている例を考えると以下のようになります。

11.3. SACK Permitted (オプション番号4)

上述のSACKオプションを有効化する場合SACK Permittedという別のオプションを使います。
3ウェイハンドシェイク時にSACK Permittedオプションを交換することでSACKを有効化できます。

12. TCPヘッダフィールドの項目落ち葉拾い

12.1. 予約フィールド

予約フィールドは将来の機能拡張に備えるために確保されたフィールドです。

余談ですが、ジョークRFC(ネタ)のRFC9401ではDTHフラグ(死フラグ)の仕様が公開されてます。

参考
RFC 9401: The Addition of the Death (DTH) Flag to TCP
お前のパケットはもう死んでいる。TCPに死亡フラグを実装してみた

12.2. 各コントロールフラグの解説

NS

ECN通知の信頼性を高める仕組み。
※RFC3540で標準化されていますが、TCPのおおもとのRFC9293には記載されていません。
RFC 3540 - Robust Explicit Congestion Notification (ECN) Signaling with Nonces

CWR

ECNによる輻輳制御が有効なときに輻輳ウィンドウの縮小を相手に伝えるフラグ

ECE

ECNによる輻輳制御が有効なときに、輻輳通知(CEを受信したとき)の受信を相手に伝えるフラグ

URG

緊急ポインタを利用する際に立てるフラグ

ACK

確認応答番号が有効であることを示すフラグ

PSH

受信側が即時上位アプリケーションにデータを渡すようにするフラグ
※通常このフラグがなくても、上位アプリケーションに即時データが渡されます。

RST

コネクションを強制的に切断する際やコネクションの拒否に利用されるフラグ

SYN

コネクションの確立に利用されるフラグ

FIN

コネクションの切断に利用されるフラグ

12.3. チェックサム

TCPのチェックサムでは受信したデータにエラーがないかの確認を行います。
疑似ヘッダ TCPヘッダ ペイロードを2バイトごとに足し合わせ、補数をとったものを設定します。

12.4. 緊急ポインタ

緊急ポインタはアプリケーションにこの範囲のデータは緊急データであることを伝えるための機能です。
URGフラグが立っているときに利用されます。
TCPの緊急ポインタは、URGフラグが有効な場合に、そのセグメント内のどの部分が緊急に処理されるべきデータであるかを示すフィールドです。
(TCPとして何か特殊なデータ処理はしません。)

おしまい

TCPの機能を体系的に書いてみました。(くぅ~疲れましたw)
この記事が多くのTCPに悩むエンジニアの助けになれば幸いです。

参考
くぅ〜疲れましたw (くぅつかれました)とは【ピクシブ百科事典】

Discussion

かずきちかずきち

とても図が見やすくて良いと思いました。
図は何ていうツールを使って作成しているんですか?

かずきちかずきち

PowerPointを使用している理由などあったら教えていただきたいです🙇

ばーやんばーやん

絵のリッチさと操作のしやすさのバランスが個人的に一番良いので利用しています!
グーグルスライドやDrawioより個人的には使いやすいです