3ウェイハンドシェイクとは?SYN・SYN-ACK・ACKを実際のバイト列で読み解く

2台の端末の間をSYN・SYN-ACK・ACKの3本の矢印が行き来する様子を表した図 テクノロジー
ネットワーク入門

3ウェイハンドシェイクとは?SYN・SYN-ACK・ACKを実際のバイト列で読み解く

TCPの接続は、3回のやり取りで始まります。この記事では「なぜ3回なのか」を押さえたうえで、SYNとACKが実際にどんなビットとして送られているのかを1バイトずつ確認し、さらに自分のPCで接続の状態が変わる瞬間を観測します。コピーして実行できる手順にしました。

1. 3ウェイハンドシェイクは「お互いの番号を教え合う儀式」

Webページを開くとき、ブラウザとサーバーはいきなりデータを送り始めるわけではありません。まず3回のパケット交換をして、通信の準備を整えます。これが3ウェイハンドシェイク(スリーウェイハンドシェイク)です。

TCPの現行仕様はRFC 9293「Transmission Control Protocol (TCP)」(2022年8月)です。1981年のRFC 793をはじめ、7本のRFCをまとめて置き換えた文書で、ここに3ウェイハンドシェイクの定義があります。

3回の内訳はこうです。

順番向き名前意味
1クライアント → サーバーSYN
(シン)
「接続したい。私の番号はこれです」
2サーバー → クライアントSYN-ACK
(シンアック)
「受け取りました。私の番号はこれです」
3クライアント → サーバーACK
(アック)
「そちらの番号も受け取りました」

なぜ2回でも4回でもなく3回なのか

TCPは「送ったデータが確実に届いたか」を番号で管理します。そのため、双方が自分の開始番号を相手に伝え、それが届いたことを相手から確認してもらう必要があります。

この「伝える」「確認してもらう」を両方向ぶん行うと、本来は4回必要です。ところが真ん中の2つ、つまり「クライアントの番号を確認した」と「サーバーの番号を伝える」は1つのパケットにまとめられます。それがSYN-ACKです。だから3回で済みます。

SYN-ACKは「2つの役割を兼ねた1枚」

SYN-ACKという名前は、SYNとACKという2つのフラグが同時に立っていることを表しています。新しいパケットの種類ではありません。「返事をしながら、同時に自分からも申し込む」という1枚のパケットです。この点は後ほど実際のビットで確認します。

この記事に出てくる用語の読み方

ネットワークの用語は英字の略語が多く、読み方が1つに決まっていないものもあります。ここでは実務で耳にすることの多い読み方を挙げます。別の読み方をする人もいるので、「こう読む人が多い」という程度に受け取ってください。

用語読み方由来・意味
TCPティーシーピーTransmission Control Protocol の略
IPアイピーInternet Protocol の略
UDPユーディーピーUser Datagram Protocol の略
RFCアールエフシーインターネットの仕様書。Request for Comments の略
SYNシンsynchronize(同期する)の略。「サイン」と読む人もいます
ACKアックacknowledgment(確認応答)の略。「エーシーケー」とも読みます
SYN-ACKシンアックSYNとACKを続けて読みます
ISNアイエスエヌ初期シーケンス番号。Initial Sequence Number の略
シーケンス番号シーケンスばんごうデータの順番を表す通し番号
オクテットオクテット8ビットのこと。ほぼ「1バイト」と同じ意味で、仕様書でよく使われます
用語読み方由来・意味
CLOSEDクローズド接続がない状態
LISTENリッスン接続を待ち受けている状態
SYN-SENTシンセントSYNを送って返事を待っている状態
SYN-RECEIVEDシンレシーブドSYNを受け取って返事をした状態
ESTABLISHEDエスタブリッシュト接続が確立した状態
ソケットソケット通信の出入口にあたるプログラム上の部品
pcapピーキャップ通信を記録したファイルの形式。packet capture の略
lsofエルエスオーエフlist open files の略。「エルソフ」と読む人もいます
netstatネットスタットnetwork statistics の略
TCBティーシービー接続の情報を保持する領域。Transmission Control Block の略
バックログバックログ未完成の接続を保持できる数の上限
SYNフラッドシンフラッドSYNを大量に送りつける攻撃
SYNクッキーシンクッキーSYNフラッドへの対策のひとつ
TLSティーエルエス通信を暗号化する仕組み。Transport Layer Security の略

2. SYN・SYN-ACK・ACKの中身を1バイトずつ見る

説明だけではイメージしづらいので、3ウェイハンドシェイクだけを含む通信記録ファイル(pcap/ピーキャップ)を自分で組み立てて、中身を読み解きます。実際の通信は一切発生しないので、安全に何度でも試せます。

手順1:ハンドシェイクのpcapを作る

以下を handshake_pcap.py として保存します。Pythonの標準ライブラリだけで動きます。

#!/usr/bin/env python3
"""3ウェイハンドシェイクだけを含むpcapを組み立てる。実際の通信は行わない。"""
import struct

CLIENT, SERVER = "192.168.10.50", "203.0.113.10"
SPORT, DPORT = 51000, 80
ISN_C, ISN_S = 1_284_002_651, 3_907_441_180   # 初期シーケンス番号

FIN, SYN, RST, PSH, ACK = 0x01, 0x02, 0x04, 0x08, 0x10

def ip2b(s): return bytes(int(x) for x in s.split("."))

def csum(b):
    if len(b) % 2: b += b"\x00"
    s = sum(struct.unpack("!%dH" % (len(b) // 2), b))
    while s >> 16: s = (s & 0xFFFF) + (s >> 16)
    return (~s) & 0xFFFF

def tcp(src, dst, sport, dport, seq, ack, flags, win):
    hdr = struct.pack("!HHIIHHHH", sport, dport, seq, ack, (5 << 12) | flags, win, 0, 0)
    pseudo = ip2b(src) + ip2b(dst) + struct.pack("!BBH", 0, 6, len(hdr))
    return hdr[:16] + struct.pack("!H", csum(pseudo + hdr)) + hdr[18:]

def ip(src, dst, payload, ident):
    h = struct.pack("!BBHHHBBH", 0x45, 0, 20 + len(payload), ident, 0x4000, 64, 6, 0) \
        + ip2b(src) + ip2b(dst)
    return h[:10] + struct.pack("!H", csum(h)) + h[12:] + payload

def eth(p):
    return b"\x02\x00\x00\x00\x00\x01" + b"\x02\x00\x00\x00\x00\x02" + b"\x08\x00" + p

packets = [
    (CLIENT, SERVER, SPORT, DPORT, ISN_C,     0,         SYN,       65535),
    (SERVER, CLIENT, DPORT, SPORT, ISN_S,     ISN_C + 1, SYN | ACK, 65535),
    (CLIENT, SERVER, SPORT, DPORT, ISN_C + 1, ISN_S + 1, ACK,       65535),
]

with open("handshake.pcap", "wb") as f:
    f.write(struct.pack("!IHHiIII", 0xA1B2C3D4, 2, 4, 0, 0, 65535, 1))
    for i, (s, d, sp, dp, seq, ack, fl, win) in enumerate(packets):
        frame = eth(ip(s, d, tcp(s, d, sp, dp, seq, ack, fl, win), 200 + i))
        f.write(struct.pack("!IIII", 1789984800, i * 200, len(frame), len(frame)) + frame)
print("handshake.pcap を作成しました(3パケット)")

手順2:中身を読み解く

次を decode_handshake.py として保存します。pcapを開いて、TCPヘッダーを項目ごとに表示するだけのプログラムです。

#!/usr/bin/env python3
"""pcapを読み、TCPヘッダーを1項目ずつ表示する。"""
import struct

NAMES = [(0x01, "FIN"), (0x02, "SYN"), (0x04, "RST"), (0x08, "PSH"),
         (0x10, "ACK"), (0x20, "URG"), (0x40, "ECE"), (0x80, "CWR")]

data = open("handshake.pcap", "rb").read()
pos, n = 24, 0
while pos < len(data):
    _, _, incl, _ = struct.unpack("!IIII", data[pos:pos + 16])
    frame = data[pos + 16:pos + 16 + incl]
    pos += 16 + incl
    n += 1
    ipv4 = frame[14:]
    ihl = (ipv4[0] & 0x0F) * 4
    src = ".".join(str(b) for b in ipv4[12:16])
    dst = ".".join(str(b) for b in ipv4[16:20])
    t = ipv4[ihl:]
    sport, dport, seq, ack, off_flags, win, cks, _ = struct.unpack("!HHIIHHHH", t[:20])
    flags = off_flags & 0x3F
    on = [nm for bit, nm in NAMES if flags & bit]
    print("── パケット%d  %s:%d → %s:%d" % (n, src, sport, dst, dport))
    print("   フラグ          : %s  (0x%03x)" % ("+".join(on), flags))
    print("   シーケンス番号  : %d" % seq)
    print("   確認応答番号    : %s" % (ack if flags & 0x10 else "%d(ACKフラグなしのため無効)" % ack))
    print("   ウィンドウサイズ: %d" % win)
    print("   TCPヘッダー長   : %d バイト" % (((off_flags >> 12) & 0xF) * 4))
    print("   生バイト(先頭14): %s" % " ".join("%02x" % b for b in t[:14]))
    print()
python3 handshake_pcap.py
python3 decode_handshake.py

実行するとこうなります。以下は実際の出力です。

── パケット1 192.168.10.50:51000 → 203.0.113.10:80 フラグ : SYN (0x002) シーケンス番号 : 1284002651 確認応答番号 : 0(ACKフラグなしのため無効) ウィンドウサイズ: 65535 TCPヘッダー長 : 20 バイト 生バイト(先頭14): c7 38 00 50 4c 88 53 5b 00 00 00 00 50 02 ── パケット2 203.0.113.10:80 → 192.168.10.50:51000 フラグ : SYN+ACK (0x012) シーケンス番号 : 3907441180 確認応答番号 : 1284002652 ウィンドウサイズ: 65535 TCPヘッダー長 : 20 バイト 生バイト(先頭14): 00 50 c7 38 e8 e6 d2 1c 4c 88 53 5c 50 12 ── パケット3 192.168.10.50:51000 → 203.0.113.10:80 フラグ : ACK (0x010) シーケンス番号 : 1284002652 確認応答番号 : 3907441181 ウィンドウサイズ: 65535 TCPヘッダー長 : 20 バイト 生バイト(先頭14): c7 38 00 50 4c 88 53 5c e8 e6 d2 1d 50 10

ここで必ず見てほしい数字

3つの数字の関係が、3ウェイハンドシェイクの本質です。

パケットシーケンス番号確認応答番号どう解釈するか
SYN1284002651(無効)クライアントが自分の開始番号を宣言した
SYN-ACK39074411801284002652「1284002651の次を待っています」=SYNを受け取った証明。同時に自分の開始番号を宣言
ACK12840026523907441181「3907441180の次を待っています」=SYN-ACKを受け取った証明

確認応答番号は「次に受け取りたい番号」です。「ここまで受け取った」ではなく「次はこれをください」と伝える形式になっています。だから相手のシーケンス番号より1つ大きい値になります。

なぜぴったり「+1」なのか

SYNパケットはデータを1バイトも運んでいません。それなのに番号が1つ進みます。RFC 9293は、SYNは最初のデータより前に置かれるものとして扱われ、暗黙にシーケンス番号を1つ消費すると説明しています。番号を持たせることで、SYN自体を再送したり確認応答したりしても混乱しないようにするためです。

接続を閉じるときのFINも同じく1つ消費します。

3. SYNフラグの正体:TCPヘッダーの8つのビット

「SYNを送る」と言いますが、SYNという特別なパケットがあるわけではありません。TCPヘッダーの中の1ビットが立っているだけです。

RFC 9293は、制御ビット(フラグ)として8つを定義しています。

フラグ読み方正式名ビット値意味
FINフィンFinish0x01送信側からのデータはもうない
SYNシンSynchronize0x02シーケンス番号を同期する
RSTアールエスティーReset0x04接続をリセットする
PSHピーエスエイチPush0x08データをすぐ上位へ渡す
ACKアックAcknowledgment0x10確認応答番号のフィールドが有効である
URGユーアールジーUrgent0x20緊急ポインタのフィールドが有効である
ECEイーシーイーECN-Echo0x40輻輳通知に使う
CWRシーダブリューアールCongestion Window Reduced0x80輻輳ウィンドウを減らしたことを示す

先ほどの生バイト出力の末尾に注目してください。3つのパケットで、それぞれ 50 02、50 12、50 10 となっていました。ここがフラグの正体です。

生バイト上位4ビット下位のフラグ結果
50 025 → ヘッダー長 5×4 = 20バイト0x02SYN だけ
50 12同上0x12 = 0x10 + 0x02ACK と SYN の両方
50 10同上0x10ACK だけ
SYN-ACKの正体がこれです

0x12 は 0x10(ACK)と 0x02(SYN)を足した値です。つまりSYN-ACKとは、同じ1バイトの中でACKのビットとSYNのビットが同時に1になっている状態にすぎません。特別なパケット種別ではなく、2つのスイッチが同時に入っているだけです。

ACKフラグには、もう1つ大事な役割があります。「確認応答番号のフィールドが有効かどうか」を示すスイッチだという点です。最初のSYNではACKフラグが立っていないため、確認応答番号の欄に何が入っていても意味を持ちません。先ほどのデコード結果で、パケット1だけ「ACKフラグなしのため無効」と表示していたのはこのためです。

4. シーケンス番号はなぜ0から始まらないのか

実験で使った開始番号は 1284002651 と 3907441180 でした。0ではありません。この最初の番号を初期シーケンス番号(ISN:Initial Sequence Number、アイエスエヌ)と呼びます。

ISNが0固定だと困ることが2つあります。

  1. 前の接続の残骸と混ざる:同じIPアドレスとポートの組み合わせで接続をやり直したとき、遅れて届いた古いパケットを新しい接続のものと取り違えるおそれがあります
  2. 第三者に推測される:次に使われる番号が分かると、接続に割り込んで偽のパケットを送り込みやすくなります

RFC 9293はこれを踏まえ、ISNを4マイクロ秒で進むタイマーの値と、接続の情報および秘密鍵から計算した値を組み合わせて決める方式を規定しています。単なる乱数でも連番でもなく、「時間とともに進み、かつ外部から推測しにくい」値になるように設計されています。

学習用のコードでは固定値でよい、ただし

この記事のスクリプトはISNを固定値で書いています。中身を追いやすくするためです。実際に通信するプログラムを書く場合、ISNを自分で決める必要はありません。OSのTCP実装が適切に生成します。ソケットを使う通常のプログラミングでISNを意識する場面はほぼありません。

5. 状態はこう変わる:CLOSEDからESTABLISHEDまで

3ウェイハンドシェイクの最中、両側はそれぞれ内部の状態を変えていきます。RFC 9293が示す流れは次のとおりです。

段階クライアント側サーバー側
開始前CLOSEDLISTEN(待ち受け中)
SYN を送った / 受けたSYN-SENTSYN-RECEIVED
SYN-ACK を受けた / ACK を受けたESTABLISHEDESTABLISHED

両方がESTABLISHEDになって、ようやくデータを送れます。HTTPのリクエストが流れ始めるのはこの後です。

なお、両側が同時にSYNを送り合う同時オープン(simultaneous open)という場合もあります。このときはどちらもSYN-SENTからSYN-RECEIVEDを経てESTABLISHEDに至ります。RFC 9293は、TCP実装が同時オープンに対応しなければならないと定めています。日常的にはまず起きませんが、「クライアントとサーバーという役割は仕様上の前提ではない」ことを示す例です。

6. 自分のPCで状態が変わる瞬間を観測する

ここからは実機です。特別なツールは不要で、Pythonと lsof(エルエスオーエフ)コマンドだけで観測できます。通信はすべて自分のPCの中(127.0.0.1)で完結します。

以下を observe.py として保存して実行してください。

#!/usr/bin/env python3
"""3ウェイハンドシェイクを自分で起こし、ソケットの状態を観測する。"""
import socket, subprocess, threading, time

PORT = 18080

def show(label):
    out = subprocess.run(["lsof", "-nP", "-iTCP:%d" % PORT],
                         capture_output=True, text=True).stdout
    print("■ %s" % label)
    lines = out.splitlines()[1:]
    for line in lines:
        print("   ", line.split("TCP ", 1)[1] if "TCP " in line else line)
    if not lines:
        print("    (該当なし)")
    print()

srv = socket.socket()
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(("127.0.0.1", PORT))
srv.listen(5)
show("1. サーバーが listen() を呼んだ直後")

held = {}
threading.Thread(target=lambda: held.setdefault("c", srv.accept()[0]), daemon=True).start()

cli = socket.socket()
t0 = time.time()
cli.connect(("127.0.0.1", PORT))          # ここで3ウェイハンドシェイクが完了する
print("    connect() の所要時間: %.2f ミリ秒\n" % ((time.time() - t0) * 1000))
time.sleep(0.3)
show("2. クライアントが connect() を呼んだ直後")

cli.close()
if "c" in held: held["c"].close()
srv.close()

実際の出力です。

■ 1. サーバーが listen() を呼んだ直後 127.0.0.1:18080 (LISTEN) connect() の所要時間: 0.15 ミリ秒 ■ 2. クライアントが connect() を呼んだ直後 127.0.0.1:18080 (LISTEN) 127.0.0.1:61447->127.0.0.1:18080 (ESTABLISHED) 127.0.0.1:18080->127.0.0.1:61447 (ESTABLISHED)

ここから3つのことが読み取れます。

  • LISTENのソケットは残ったままです。接続が成立すると、待ち受け用とは別に新しいソケットの組が作られます。だからサーバーは次の接続を受け付け続けられます
  • ESTABLISHEDが2行あるのは、自分のPCの中で通信しているためです。クライアント側とサーバー側の両方が同じマシンに存在しています
  • ハンドシェイクは0.15ミリ秒で完了しました。同じPC内なので一瞬です。相手が遠いほどこの時間は伸び、往復の遅延がそのまま接続開始の待ち時間になります
netstat ではなく lsof を使っている理由

筆者のmacOS環境では、netstat -an で 127.0.0.1 のソケットもSYN_SENT状態も表示されませんでした。lsof -nP -iTCP では両方とも確認できたため、この記事では lsof を使っています。Linuxでは ss -tan が見やすく、こちらでも同じ状態名が確認できます。

7. 失敗するとどうなるか:即座に拒否と、返事なし

ハンドシェイクが成立しないパターンは、大きく2つあります。どちらも実際に測りました。

ケース1:ポートが閉じている → RSTで即座に拒否

誰も待ち受けていないポートにSYNを送ると、相手はRSTフラグを立てたパケットを返します。RFC 9293は、接続が存在しない状態に届いたセグメントに対しては、それ自体がRSTである場合を除いてリセットを返すと定めています。

import socket, time
s = socket.socket()
t0 = time.time()
try:
    s.connect(("127.0.0.1", 18081))     # 誰も待っていないポート
except OSError as e:
    print("%.2f ミリ秒で %s(errno=%s: %s)"
          % ((time.time() - t0) * 1000, type(e).__name__, e.errno, e.strerror))
0.13 ミリ秒で ConnectionRefusedError(errno=61: Connection refused)

0.13ミリ秒で結果が返りました。「接続を拒否されました」というエラーは、相手がきちんと返事をしてくれた証拠です。プログラムから見ると失敗ですが、TCPとしては正常な動作です。

ケース2:返事がまったく来ない → 沈黙のまま再送を続ける

次に、応答しないアドレスへ接続してみます。192.0.2.1 はRFC 5737でドキュメント用に予約されたアドレスで、通常どこにも到達しません。

import socket, time
s = socket.socket()          # タイムアウトを指定しない=OS任せ
t0 = time.time()
try:
    s.connect(("192.0.2.1", 80))
except OSError as e:
    print("%.1f 秒後に %s(errno=%s)" % (time.time() - t0, type(e).__name__, e.errno))
75.0 秒後に TimeoutError(errno=60)

75秒かかりました。この間、OSはSYNを何度も再送しながら返事を待ち続けています。接続中に別のターミナルで lsof を実行すると、その状態が見えます。

Python 42891 kenko 3u IPv4 … TCP 192.168.0.10:61437->192.0.2.1:80 (SYN_SENT)

SYN_SENT。第5章の表に出てきた状態が、そのまま現れています。SYNを送ったが、まだSYN-ACKを受け取っていない状態です。

状況相手の反応結果実測
正常SYN-ACK を返すESTABLISHED0.15ミリ秒(自PC内)
ポートが閉じているRST を返す接続拒否のエラー0.13ミリ秒
到達しない・遮断されている何も返さないSYN_SENT のまま、やがてタイムアウト75.0秒
トラブル対応で効く知識です

「すぐエラーになる」のか「長く固まる」のかで、原因の見当がつきます。即座に拒否されるなら相手まで届いており、サービスが動いていないだけ。長時間固まるならパケットが相手に届いていないか、届いても黙って捨てられている可能性が高い、という切り分けです。ファイアウォールの設定ミスを調べるときの最初の手がかりになります。

8. SYNフラッド攻撃とSYNクッキー

3ウェイハンドシェイクを理解すると、古典的な攻撃の仕組みもそのまま理解できます。

サーバーはSYNを受け取った時点でSYN-RECEIVED状態になり、接続の情報を記録する領域(TCB:Transmission Control Block、ティーシービー)を確保します。まだ3回目のACKは届いていないので、これはハーフオープン(半開き)の接続です。

この「まだ完成していない接続」を保持できる数には上限があります。これをバックログと呼びます。

SYNフラッド攻撃(シンフラッド)は、送信元アドレスを偽ったSYNを大量に送りつけ、このバックログを埋め尽くす攻撃です。RFC 4987「TCP SYN Flooding Attacks and Common Mitigations」(2007年8月、Informational)は、この攻撃がネットワークの帯域を潰すのではなく、ハーフオープン接続の枠を使い切らせることを狙うものだと説明しています。枠が尽きると、正規の利用者の接続が受け付けられなくなります。

同RFCが挙げている主な対策は次のとおりです。

対策考え方
SYNクッキー
(シンクッキー)
状態を保存せず、必要な情報をシーケンス番号に埋め込んで返す。3回目のACKが来た時点で復元するため、ハーフオープンの領域を使わない
SYNキャッシュ接続が完成するまでは最小限の情報だけを持つ
フィルタリング送信元アドレスの詐称を入口で防ぐ
バックログの拡大枠を増やす。ただし限界がある
SYN-RECEIVEDタイマーの短縮早く諦めて枠を空ける。本気の攻撃は防ぎきれない
古いハーフオープンの再利用枠が満杯なら古いものから捨てる
ファイアウォール・プロキシ接続の確立を手前の機器に肩代わりさせる

ここで効いているのが、第4章で触れたISNの設計です。SYNクッキーは「サーバーが返すシーケンス番号」に情報を埋め込むという仕組みなので、そもそもISNの値がサーバーの裁量で決められることが前提になっています。ハンドシェイクの各フィールドが何のためにあるのかを知っていると、対策の理屈まで一本につながります。

通信を監視して不審な通信を見つける仕組みについては、IDSとIPSの違いをゼロから解説でSuricataを実際に動かしながら解説しています。

9. よくある質問

Q. 3ウェイハンドシェイクは毎回必要ですか?

TCP接続を新しく作るたびに必要です。ただし1本の接続を使い回せば、その回数は減らせます。HTTPで「Keep-Alive」や接続の再利用が重視されるのは、接続のたびに往復のやり取りが発生し、その分だけ待ち時間が増えるためです。実測のとおり同じPC内なら0.15ミリ秒ですが、遠いサーバーとの間では往復の遅延がそのまま上乗せされます。

Q. UDPにもハンドシェイクはありますか?

ありません。UDPは接続という概念を持たず、いきなりデータを送ります。そのため到達確認も順序の保証もない代わりに、開始時の往復が不要です。「TCPは3回の挨拶をしてから話し始める、UDPはいきなり話しかける」と考えると違いが整理しやすくなります。

Q. HTTPSだと、さらにやり取りが増えるのですか?

増えます。TCPの3ウェイハンドシェイクが終わってから、暗号化のための別のやり取り(TLSハンドシェイク/ティーエルエス)が始まります。「ハンドシェイク」という言葉は両方に使われるので、話題がTCPの接続確立なのか暗号化の準備なのかを区別して読むと混乱しません。この記事で扱っているのはTCPのほうです。

Q. 接続を閉じるときも3回ですか?

閉じるときは基本的に4回です。FINとACKをお互いに送り合うため、FIN → ACK → FIN → ACK という流れになります。開くときにSYNとACKを1枚にまとめられたのに対し、閉じるときは「こちらはもう送るものがないが、そちらはまだ送るかもしれない」という状態があり得るため、まとめられないのが理由です。

Q. 次は何を学べばいいですか?

この記事の内容が身についたら、① ウィンドウサイズと再送の仕組み(今回は65535という値を見ただけです)、② 接続を閉じる手順とTIME_WAIT状態、③ TLSハンドシェイクの流れ、の順が自然です。いずれも「番号で管理する」というTCPの考え方が土台になります。

10. まとめ

この記事の要点
  • 3ウェイハンドシェイクは番号の交換。双方が開始番号を宣言し、相手に受け取りを確認してもらう。真ん中の2つが1枚にまとまるので3回で済む
  • SYN-ACKは特別なパケットではない。1バイトの中でSYN(0x02)とACK(0x10)のビットが同時に立ち、0x12 になっているだけ
  • 確認応答番号は「次に欲しい番号」。SYNはデータを運ばないのに番号を1つ消費するため、ぴったり+1になる
  • 状態はCLOSED→SYN-SENT→ESTABLISHED(サーバー側はLISTEN→SYN-RECEIVED→ESTABLISHED)。lsof で実際に目で見える
  • 失敗の仕方で原因が分かる。即座に拒否ならRSTが返っており相手まで届いている。長く固まるならパケットが黙って捨てられている

今日やれる最初の一歩は、第6章の observe.py を実行して LISTEN と ESTABLISHED を自分の目で見ることです。教科書の図が、自分のPCの中で本当に起きていることだと確認できます。

そのうえで第2章のデコードを動かせば、50 02 という2バイトがSYNパケットそのものだと分かります。そこまで来れば、3ウェイハンドシェイクはもう暗記する対象ではなくなります。

タイトルとURLをコピーしました