Featured image of post 3ウェイハンドシェイクとは?TCPの接続確立の仕組みをやさしく解説【第30回】

3ウェイハンドシェイクとは?TCPの接続確立の仕組みをやさしく解説【第30回】

この記事でわかること

  • なぜTCPはデータを送る前に「接続確立」が必要なのか
  • SYN・SYN-ACK・ACKの3ステップそれぞれの意味と役割
  • 2回ではなく「3回」必要な数学的な理由
  • シーケンス番号の初期値(ISN)がハンドシェイクで交換される意味
  • LISTEN・SYN_SENT・ESTABLISHEDなど接続の状態遷移
  • 接続を切断する4ウェイFINハンドシェイクの流れ
  • TIME_WAIT状態とは何か、なぜすぐに接続を閉じないのか

はじめに

前回の記事でTCPとUDPのトレードオフを学んだとき、3ウェイハンドシェイクについて「SYN・SYN-ACK・ACKの3ステップ」と電話の例えで触れました。

でも「なぜ3回なのか」「その3回の間に何が行われているのか」はまだ深掘りしていません。3ウェイハンドシェイクは、ネットワークを学ぶ上で最も重要な仕組みのひとつです。UDPとの違いを理解するにも、セキュリティの問題を把握するにも、ここを押さえておくと後々の理解が変わります。


なぜ「接続確立」が必要なのか

UDPと比べると、TCPの接続確立の意味がよくわかります。

UDPはデータをそのまま送りっぱなしにします。相手が待ち受けているかどうか、送信先が存在するかどうか、確認せずに送ります。

TCPはデータを送る前に「準備はできていますか?」「こちらも準備できました」という確認を交わします。この確認には2つの目的があります。

① 双方向の通信が成立するかを確かめる

送信側から受信側への通信が届くか、受信側から送信側への通信が届くか——両方向を事前に確認します。一方向しか通じないネットワーク経路や設定ミスを、最初の段階で検出できます。

② 通信に必要な情報を共有する

シーケンス番号の初期値(後述)など、これから始まるデータ転送に必要な情報をハンドシェイクの中で交換します。


3ウェイハンドシェイクの全体像

3ウェイハンドシェイクの流れを図で確認してから、各ステップを詳しく見ていきます。

クライアント                    サーバー

     │                              │
     │ ─── SYN ──────────────────► │  ① 接続リクエスト
     │                              │
     │ ◄────────────── SYN-ACK ─── │  ② 受信確認+接続受諾
     │                              │
     │ ─── ACK ──────────────────► │  ③ 最終確認
     │                              │
     │    <接続確立(ESTABLISHED)>    │
     │                              │
     │ ─── データ ───────────────► │  データ転送開始

ステップ① SYN——「通信を始めたい」

最初にクライアントがサーバーへ SYN パケット を送ります。

  • SYN:Synchronize(シンクロナイズ)→「同期する」「タイミングを合わせる」という意味

SYNパケットに含まれる主な情報:

  • SYNフラグ:「これは接続リクエストです」という印
  • 初期シーケンス番号(ISN):クライアントが選んだランダムな数値(詳しくは後述)

この時点でクライアントの状態は SYN_SENT(SYNを送った、返事を待っている)になります。


ステップ② SYN-ACK——「受け取った、こちらも準備できた」

SYNを受け取ったサーバーは SYN-ACK パケット を返します。

  • ACK:Acknowledge(アクノレッジ)→「受け取ったことを確認する」「認める」という意味

SYN-ACKパケットは2つの意味を1つのパケットに詰め込んでいます。

ACKの部分:「あなたのSYNを受け取りました」という確認
SYNの部分:「こちらからの接続もお願いします」という要求

ACK番号 = クライアントのISN + 1

「あなたのシーケンス番号の続きから受け取ります」という意味で、受信を確認した証拠になります。

また、SYN-ACKにはサーバー自身の初期シーケンス番号(ISN)も含まれています。

この時点でサーバーの状態は SYN_RECEIVED(SYNを受け取り、SYN-ACKを送った)になります。


ステップ③ ACK——「こちらも受け取った、始めましょう」

SYN-ACKを受け取ったクライアントは、最後に ACK パケット をサーバーへ返します。

ACK番号 = サーバーのISN + 1

「あなたのSYN-ACKを受け取りました。あなたのシーケンス番号の続きから受け取ります」という確認です。

このACKがサーバーに届いた瞬間、双方の状態が ESTABLISHED(エスタブリッシュド) になります。

  • Established:「確立された」「安定した状態に置かれた」という意味

接続が正式に確立し、データ転送を始められる状態です。


なぜ「3回」なのか——2回では足りない理由

「2回のやりとりで十分では?」という疑問は自然です。なぜ3回必要なのかを、確認できる情報の観点から整理します。

1回目(SYN)の後でわかること

主体 確認できること
クライアント 何もわからない
サーバー 「クライアントからサーバーへの通信は届く」

2回目(SYN-ACK)の後でわかること

主体 確認できること
クライアント 「サーバーからクライアントへの通信は届く」「サーバーは自分のSYNを受け取った」
サーバー 「クライアントからサーバーへの通信は届く」←(変わらず)

ここで止まると問題があります。サーバーは「自分のSYN-ACKがクライアントに届いたかどうか」がわかりません。クライアント→サーバー方向は確認できていますが、サーバー→クライアント方向がクライアントに届いているかどうか、サーバーは知る術がないのです。

3回目(ACK)の後でわかること

主体 確認できること
クライアント 「クライアント↔サーバー双方向の通信は成立する」
サーバー 「クライアント↔サーバー双方向の通信は成立する」← 追加

3回目のACKが届いて初めて、サーバーは「自分のSYN-ACKはクライアントに届いており、双方向の通信が成立している」と確認できます。

確認できる通信路:

1回後:クライアント → サーバー ✓
2回後:クライアント → サーバー ✓  サーバー → クライアント ✓(クライアントだけ知っている)
3回後:クライアント → サーバー ✓  サーバー → クライアント ✓(双方が知っている)

「双方が双方向の通信を確認する」ために最低3回のやりとりが必要というのが、数学的な必然です。

手紙のやりとりで例えるなら:

1通目(SYN):「この手紙は届きますか?」
2通目(SYN-ACK):「届きました。こちらの手紙はあなたに届きますか?」
3通目(ACK):「届きました。これで双方向の連絡が取れると確認しました。」

2通でやめると、2通目を送った側が「自分の手紙が届いたかどうか」を確認できないままになります。


シーケンス番号の初期値(ISN)はなぜランダムなのか

ハンドシェイクのステップで「初期シーケンス番号(ISN)」という言葉が出てきました。

  • ISN:Initial(イニシャル)= 「最初の」、Sequence(シーケンス)= 「順序」、Number(ナンバー)= 「番号」
  • 合わせて「最初のシーケンス番号」

TCPでデータを送るとき、パケットには「何バイト目のデータか」を示すシーケンス番号が付けられます。ハンドシェイクはそのシーケンス番号の「スタート地点」をお互いに伝え合う場でもあります。

ISNはランダムな値が使われます。理由はセキュリティです。

もしも ISN が「毎回0から始まる」など予測できる値だったとします。攻撃者は「次のISNはこの値だろう」と推測し、偽のパケットを送り込むことができてしまいます。「あたかも通信相手から来たパケット」として紛れ込ませる攻撃です。ISNをランダム化することで、こうした予測攻撃を防いでいます。


接続の状態遷移

TCPの接続には、段階ごとに名前のついた「状態」があります。

【クライアント側の状態遷移】

CLOSED(初期状態)
  ↓  SYN送信
SYN_SENT(SYNを送り、SYN-ACKを待っている)
  ↓  SYN-ACK受信 → ACK送信
ESTABLISHED(接続確立、データ転送中)
【サーバー側の状態遷移】

LISTEN(接続を待ち受けている)
  ↓  SYN受信 → SYN-ACK送信
SYN_RECEIVED(SYNを受け取り、ACKを待っている)
  ↓  ACK受信
ESTABLISHED(接続確立、データ転送中)

netstat -an を実行すると、これらの状態をリアルタイムで確認できます。ウェブサーバーに接続中であれば ESTABLISHED が、サーバー側のポートには LISTEN が表示されます。接続量が多いサーバーでは SYN_RECEIVED が大量に並ぶこともあります。


接続の切断——4ウェイFINハンドシェイク

接続を終了するときは、4ウェイのFINハンドシェイクを使います。

  • FIN:Finish(フィニッシュ)→「終わる」「完了する」という意味
クライアント                    サーバー

     │ ─── FIN ──────────────────► │  ① 「私は送り終わりました」
     │                              │
     │ ◄──────────────────── ACK ─ │  ② 「わかりました」
     │                              │
     │ ◄──────────────────── FIN ─ │  ③ 「私も送り終わりました」
     │                              │
     │ ─── ACK ──────────────────► │  ④ 「わかりました」
     │                              │
     │        <接続終了>               │

ハンドシェイクが3回なのに対して、切断が4回になるのは「送り終わった」がそれぞれ独立しているためです。クライアントが送り終わっても、サーバーはまだ送るデータが残っているかもしれません。「私は終わった(FIN)」「わかった(ACK)」のやりとりをそれぞれ独立して行うので、4ステップになります。


TIME_WAIT状態——なぜすぐに閉じないのか

4ウェイFINの最後、クライアントがACKを送った後、すぐに接続が閉じるわけではありません。しばらくの間 TIME_WAIT 状態になります。

  • TIME_WAIT:Time(タイム)= 「時間」、Wait(ウェイト)= 「待つ」→「一定時間待機する状態」

なぜすぐに閉じないのでしょうか。

最後に送ったACKが、ネットワークの途中で消えてしまうかもしれません。サーバー側がACKを受け取れなかった場合、サーバーはFINを再送してきます。TIME_WAIT中であれば、クライアントはもう一度ACKを返すことができます。

もしもTIME_WAITなしで即座に閉じていたら、サーバーからの再送FINを「知らない接続からのパケット」として処理してしまい、切断が正しく完了しません。

TIME_WAITの期間はOSによって異なりますが、多くは60秒〜120秒程度です。

netstat -anTIME_WAIT 状態のエントリが大量に見えることがあります。多数の短い接続を繰り返すウェブサーバーやAPIサーバーでよく起きます。「接続が終わっているのにポートが使えない」と感じる原因の多くがこのTIME_WAITです。


SYNフラッド攻撃——ハンドシェイクを悪用した攻撃

3ウェイハンドシェイクの仕組みを理解すると、これを悪用した攻撃の原理も見えてきます。

SYNフラッド攻撃は、サーバーにSYNパケットを大量に送りつけることで、サーバーを機能不全に陥らせる攻撃です。

  • Flood(フラッド):「洪水」という意味。大量のSYNパケットで溢れさせることから名付けられています

攻撃の流れはこうです。

攻撃者:偽の送信元IPアドレスで大量のSYNを送る
サーバー:SYNを受け取るたびに SYN_RECEIVED 状態のエントリを作り SYN-ACK を返す
(偽のIPアドレスにはACKが返ってこないため、ずっと SYN_RECEIVED 状態が残り続ける)
→ サーバーのメモリや接続管理テーブルが枯渇
→ 正規のユーザーからの接続が確立できなくなる

「SYN-ACKを送った後、ACKを待っている間はリソースを使い続ける」というTCPの設計が悪用されています。現在ではSYN Cookieという技術で対策されていることが多く、ACKが来る前はサーバーがリソースをほぼ使わないようになっています。


netstatで状態を確認してみよう

現在のTCP接続状態は netstat -an で確認できます。

netstat -an

Windowsの出力例

Proto  Local Address          Foreign Address        State
TCP    0.0.0.0:22             0.0.0.0:*              LISTEN
TCP    192.168.1.10:54231     142.250.185.46:443     ESTABLISHED
TCP    192.168.1.10:54298     52.84.10.20:443        TIME_WAIT

Linuxで状態ごとの接続数を集計する

ss -an | grep tcp | awk '{print $2}' | sort | uniq -c | sort -rn

次のような出力が得られます。

  42 ESTABLISHED
   8 TIME_WAIT
   3 LISTEN
   1 SYN_SENT

ESTABLISHED が接続中の数、TIME_WAIT が切断待機中の数です。ウェブブラウジング中に実行すると ESTABLISHED でHTTPS(443番)の接続がいくつか並ぶはずです。


まとめ

  • 3ウェイハンドシェイクは「SYN → SYN-ACK → ACK」の3ステップでTCPの接続を確立する手順
  • SYN(接続リクエスト)→ SYN-ACK(受信確認+接続受諾)→ ACK(最終確認)の順で進む
  • 3回必要な理由:「クライアント→サーバー」「サーバー→クライアント」の双方向通信を双方が確認するために最低3回のやりとりが必要。2回では片方しか確認できない
  • ハンドシェイク中に**ISN(初期シーケンス番号)**を交換する。ランダム化することで攻撃者による予測を防いでいる
  • 接続には状態がある。LISTEN(待ち受け)→SYN_SENT / SYN_RECEIVED(ハンドシェイク中)→ESTABLISHED(確立済み)と遷移する
  • 切断は4ウェイFINハンドシェイク(FIN→ACK→FIN→ACK)で行う。送受信それぞれが「終わった」を独立して通知するため4回になる
  • 切断後しばらく続くTIME_WAIT状態は、最後のACKが届かなかった場合の再送に対応するための待機期間
  • ハンドシェイクの設計を悪用したSYNフラッド攻撃は、大量のSYNで接続テーブルを枯渇させる。現代はSYN Cookieで対策されている