コネクション — TCP の 3 ウェイハンドシェイクと切断
コネクションの正体は両端が合意した番号の数え始め。SYN → SYN/ACK → ACK がなぜ 3 回必要なのか、FIN と TIME_WAIT で何を守っているのかを追う。
「コネクションを張る」の正体は何か
connect() が返ってくれば繋がった、くらいの感覚で普段は困りません。でも障害のときは「繋がらない」の中身 — 拒否されたのか、応答が無いのか、確立後に切れたのか — を区別できないと前に進めません。そのためには「コネクションを張る」が物理的には何なのかを知る必要があります。
答えは意外と地味です。経路上に専用線が確保されるわけではなく、途中のルータは何も覚えません。コネクションとは、両端の 2 台がそれぞれメモリ上に持つ「この 4 つ組の相手と、どの番号までやり取りしたか」という帳簿の対にすぎません。だからコネクションを作る儀式でやるべきことも 1 つだけ — お互いの「数え始めの番号」を伝え合い、確かに聞いたと確認し合うことです。
この数え始めの番号を初期シーケンス番号(ISN)と呼びます。1 から数え始めない理由は第 5 問の TIME_WAIT とも通じます — 直前まで同じ 4 つ組で通信していた古いパケットと紛れないよう、毎回予測しにくい値から始めるのです。
3 ウェイハンドシェイク。ISN はクライアント 5000、サーバ 9000 の例です。ack 番号が常に「相手の番号 + 1 = 次に欲しい番号」になっていることを確かめてください。
3 つのパケットを 1 つずつ読む
クライアントの ISN を 5000、サーバの ISN を 9000 とした例で追います。
seq = 5000。「接続したい。私は 5000 から数え始める」。SYN はデータを運ばないが、番号を 1 つ消費するseq = 9000, ack = 5001。「5001 まで聞いた(= あなたの SYN を受け取った)。私は 9000 から数え始める」— 確認と宣言の相乗りseq = 5001, ack = 9001。「あなたの数え始めも聞いた」。ここで両者とも ESTABLISHED になり、データを送れるなぜ 3 回か。TCP は双方向に独立した 2 本のバイトストリームを持つので、方向ごとに「数え始めの宣言(SYN)」と「聞いたという確認(ACK)」が必要です。役割は合計 4 つですが、真ん中の 2 つ(サーバの ACK とサーバの SYN)は 1 パケットに相乗りできるので、最小で 3 パケット。2 回では「サーバの宣言が届いた」ことを誰も確認しておらず、4 回は無駄が 1 つある、というわけです。
小問 2-1 — 3 ウェイハンドシェイクを 1 パケットずつ
3 つのパケットそれぞれの「言っていること」を確認します。番号の問題は紙に書いて計算してください。
Q1. 3 ウェイハンドシェイクの 2 番目のパケット(サーバが返す SYN/ACK)が言っていることとして正しいものはどれですか。
SYN/ACK は 2 つの役割の相乗りです。ACK 部分がクライアントの SYN への確認、SYN 部分がサーバ自身の数え始め(サーバ側 ISN)の宣言。TCP は双方向にそれぞれ独立したバイトストリームを持つので、両方向それぞれに SYN と ACK が 1 回ずつ必要です。それを 3 パケットに詰めたのがこの形です。
Q2. クライアントが初期シーケンス番号 ISN = 5000 で SYN を送りました。サーバが返す SYN/ACK の ack 番号はいくつですか。(SYN はデータを運ばないが、シーケンス番号を 1 つ消費する)
ack 番号は「次に欲しい番号」です。SYN はシーケンス番号を 1 つ消費する約束なので、seq = 5000 の SYN を受け取ったサーバが次に欲しいのは 5001。よって ack = 5001 です。この「次に欲しい番号を返す」ルールは第 3 章のデータ転送でもまったく同じ形で使います。
Q3. ハンドシェイクが 2 回(SYN → SYN/ACK で終わり)では成立しない理由として正しいものはどれですか。
2 回で終わると、サーバは自分の SYN/ACK が届いたか分からないまま「確立した」ことにするしかありません。遅れて届いた古い SYN にも即座にコネクションを作ってしまい、相手のいない幽霊コネクションを抱えます。3 回目の ACK は「サーバの数え始めも確かに聞いた」という確認です。双方向 2 本のストリームそれぞれの SYN + ACK(計 4 役)を相乗りで 3 パケットに圧縮した、これが最小回数です。暗号鍵の交換は TLS の仕事で TCP には含まれません。
切断は 4 役 — 片方向ずつ店じまいする
切断が確立より 1 手多いのは、相乗りできるとは限らないからです。「もう送るものがない」という宣言 FIN は、自分から相手への方向だけを閉じます。相手にはまだ送り残しがあるかもしれないので、FIN を送った後も受信は続けられます(ハーフクローズ)。双方向それぞれに FIN と ACK が要るので、基本形は 4 パケットです。
そして先に FIN を送った側は、すべて終わった後も TIME_WAIT 状態でしばらく(多くの実装で 30 秒〜数分)残ります。理由は 2 つあります。
もう 1 つ、行儀の悪い終わり方が RST(リセット)です。「そんなコネクションは知らない/今すぐ壊す」という一方的な通告で、待ち受けのないポートへの SYN、クラッシュしたプロセス宛のデータなどに対して返ります。FIN は合意の店じまい、RST は問答無用の破棄 — キャプチャでこの 2 つを見分けられるだけで、障害の景色は大きく変わります。
小問 2-2 — 切断と TIME_WAIT、そして RST
始まりより終わりのほうが奥深いのが TCP です。実務で出会う頻度も切断側のほうが上です。
Q4. TCP の切断で FIN が双方向に 1 回ずつ(合計 2 回、それぞれに ACK)必要な理由はどれですか。
FIN は「私からあなたへの方向は、もう送るものがない」という片方向の宣言です。相手にはまだ送り残しがあるかもしれないので、自分が FIN を送った後も相手のデータを受信し続けられます(ハーフクローズ)。双方向それぞれの FIN + ACK で合計 4 パケット(相乗りで 3 になることも)— 確立が 3 回で済むのに切断が 4 回なのは、この「片方向ずつ閉じる」設計のためです。
Q5. 先に FIN を送って切断した側は、すぐには消えず TIME_WAIT 状態でしばらく待ちます。この待ち時間の目的として正しいものはどれですか。
TIME_WAIT の役割は 2 つ。(1) 自分が返した最後の ACK が消失すると相手は FIN を再送してくるので、応答できるよう残る。(2) ネットワーク上を漂う古いセグメントが寿命で消えるまで待ち、同じ (送信元 IP, 送信元ポート, 宛先 IP, 宛先ポート) を再利用した新しいコネクションに昔のデータが紛れ込む事故を防ぐ。つまり故障ではなく約束を最後まで守るための正常な状態です。第 7 章で「TIME_WAIT 大量発生」のケースを扱います。
Q6. 誰も待ち受けていないポートへ SYN を送った場合と、ファイアウォールがパケットを黙って捨てる場合の、クライアント側から見た違いとして正しいものはどれですか。
待ち受けの無いポートに SYN が届くと、ホストの OS は RST(リセット)を即座に返し、クライアントは Connection refused ですぐ失敗します。一方、黙って捨てる(drop する)ファイアウォールでは応答が何も無いので、クライアントは SYN を再送しながら待ち続け、最後にタイムアウトします。「即座の拒否 = 相手ホストまでは届いている」「タイムアウト = 応答が返ってこない」— この切り分けは第 7 章のケース演習で主役になります。
この章で持ち帰ること
- コネクションの正体は両端が持つ帳簿の対。確立とは「数え始めの番号(ISN)」の合意
- ハンドシェイクは双方向分の SYN + ACK の 4 役を 3 パケットに相乗りさせた最小形。ack は常に「次に欲しい番号」
- 切断は FIN で片方向ずつ。TIME_WAIT は故障ではなく、最後の ACK の保険と古いセグメントの毒抜き
- RST 即返し = 届いた上で拒否、無応答タイムアウト = そもそも応答が返らない。切り分けの第一歩
次章では、確立した後の主役 — シーケンス番号と ACK 番号がデータ転送でどう動くか — を手計算で追います。