この記事でわかること
- なぜ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 -an で TIME_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で対策されている