3-Way Handshake
TCP 연결을 수립할 때 클라이언트와 서버가 SYN → SYN-ACK → ACK 세 단계의 패킷을 교환하여 신뢰성 있는 양방향 통신을 준비하는 과정입니다.
왜 필요한가
TCP는 신뢰성 있는 통신을 보장해야 함
→ 데이터를 보내기 전에 먼저 확인해야 할 것들:
1. 상대방이 살아있는가?
2. 상대방이 데이터를 받을 준비가 되었는가?
3. 양쪽 모두 보내기·받기가 가능한가?
4. 서로의 초기 시퀀스 번호를 알고 있는가?
→ 이 모든 것을 확인하는 과정이 3-way handshake
전체 흐름
클라이언트 서버
(CLOSED) (LISTEN)
│ │
│ ① SYN (seq=100) │
│ ──────────────────────────────────→ │
│ "연결하고 싶어, 내 시퀀스 번호는 100이야" │
│ │
(SYN_SENT) │
│ │
│ ② SYN + ACK (seq=300, ack=101) │
│ ←────────────────────────────────── │
│ "좋아, 내 시퀀스 번호는 300이야, │
│ 너의 100 잘 받았어, 다음엔 101 보내줘" │
│ │
│ (SYN_RCVD)
│ │
│ ③ ACK (seq=101, ack=301) │
│ ──────────────────────────────────→ │
│ "너의 300 잘 받았어, 다음엔 301 보내줘" │
│ │
(ESTABLISHED) (ESTABLISHED)
│ │
│ ◄────── 데이터 전송 시작 ──────► │
각 단계 상세
Step 1: SYN (클라이언트 → 서버)
┌─── TCP 세그먼트 ────────────────────┐
│ SYN 플래그: 1 (ON) │
│ ACK 플래그: 0 (OFF) │
│ seq: 100 (ISN, 초기 시퀀스) │
│ ack: 없음 │
└──────────────────────────────────────┘
클라이언트: CLOSED → SYN_SENT
서버: LISTEN (변화 없음)
| 항목 | 설명 |
| 목적 | 연결 요청 + 클라이언트 ISN 전달 |
| SYN 플래그 | "동기화하자"는 의미 (Synchronize) |
| seq=100 | 클라이언트의 초기 시퀀스 번호 (ISN) |
Step 2: SYN-ACK (서버 → 클라이언트)
┌─── TCP 세그먼트 ────────────────────┐
│ SYN 플래그: 1 (ON) │
│ ACK 플래그: 1 (ON) │
│ seq: 300 (서버의 ISN) │
│ ack: 101 (클라이언트 seq+1) │
└──────────────────────────────────────┘
클라이언트: SYN_SENT (변화 없음)
서버: LISTEN → SYN_RCVD
| 항목 | 설명 |
| 목적 | 연결 수락 + 서버 ISN 전달 + 클라이언트 SYN 확인 |
| SYN | 서버도 동기화 요청 |
| ACK | 클라이언트의 SYN을 잘 받았다는 응답 |
| seq=300 | 서버의 초기 시퀀스 번호 |
| ack=101 | "너의 100번까지 받았어, 다음엔 101번 보내줘" |
Step 3: ACK (클라이언트 → 서버)
┌─── TCP 세그먼트 ────────────────────┐
│ SYN 플래그: 0 (OFF) │
│ ACK 플래그: 1 (ON) │
│ seq: 101 │
│ ack: 301 (서버 seq+1) │
└──────────────────────────────────────┘
클라이언트: SYN_SENT → ESTABLISHED
서버: SYN_RCVD → ESTABLISHED
| 항목 | 설명 |
| 목적 | 서버의 SYN-ACK에 대한 확인 응답 |
| seq=101 | 서버가 ack=101로 요청했으므로 101 전송 |
| ack=301 | "너의 300번까지 받았어, 다음엔 301번 보내줘" |
상태 변화 전체 정리
클라이언트 패킷 서버
───────── ───────── ─────────
CLOSED LISTEN
│ │
│── SYN ──────────────→ │
SYN_SENT │
│ │
│ ←────────── SYN+ACK ──│
│ SYN_RCVD
│ │
│── ACK ──────────────→ │
ESTABLISHED ESTABLISHED
| 상태 | 의미 |
| CLOSED | 연결 없음 |
| LISTEN | 서버가 연결 대기 중 |
| SYN_SENT | 클라이언트가 SYN 보내고 응답 대기 |
| SYN_RCVD | 서버가 SYN 받고 SYN-ACK 보낸 후 대기 |
| ESTABLISHED | 연결 수립 완료, 데이터 전송 가능 |
ISN (Initial Sequence Number)이 랜덤인 이유
[ 만약 ISN이 항상 0이면 ]
이전 연결: 클라이언트(seq=50) ──→ 서버
연결 종료 후 ...
네트워크에 남아있던 지연 패킷: seq=50이 뒤늦게 도착
새 연결: 클라이언트(seq=0부터 시작)
서버: "seq=50? 이건 새 연결의 패킷이야" → 잘못된 데이터 수신
[ ISN이 랜덤이면 ]
이전 연결: ISN=38291 → seq=38341 패킷 지연
새 연결: ISN=72058 → seq 범위가 완전히 다름
서버: "seq=38341? 이건 현재 연결과 무관" → 무시 O
| 이유 | 설명 |
| 이전 연결 패킷 구분 | 지연된 이전 패킷이 새 연결에 혼입되는 것 방지 |
| 보안 | 예측 가능한 ISN은 TCP 세션 하이재킹 공격에 취약 |
| 고유성 | 각 연결이 고유한 시퀀스 공간을 가짐 |
Sequence Number와 Acknowledgment Number
seq와 ack의 관계:
ack = 상대방이 보낸 seq + 받은 데이터 바이트 수
3-way handshake에서는 SYN 자체가 1바이트를 소비한다고 간주
→ ack = 상대방 seq + 1
클라이언트 서버
seq=100 (SYN) ──────────────→
ack = 100 + 1 = 101
←────────────── seq=300, ack=101 (SYN+ACK)
ack = 300 + 1 = 301
seq=101, ack=301 (ACK) ────→
연결 후 데이터 전송:
seq=101, 50바이트 전송 ─────→
ack = 101 + 50 = 151
←────────────── ack=151 ("151번부터 보내줘")
3-way인 이유 — 왜 2-way가 아닌가
2-way의 문제
[ 2-way handshake ]
클라이언트 서버
│── SYN ───→ │ ① 연결 요청
│ ←── ACK ───│ ② 서버 수락
(ESTABLISHED) (ESTABLISHED)
문제 시나리오:
클라이언트가 SYN을 보냈지만 네트워크 지연
→ 타임아웃 → 재전송 SYN
→ 원래 SYN이 뒤늦게 도착
│── SYN (원본, 지연) ────→ │
│── SYN (재전송) ──────→ │
│ ←── ACK (재전송) ─│ 정상 연결 O
│ │
│ ... 시간 경과 ... │
│ │
│── SYN (원본, 뒤늦게 도착) → │
│ ←── ACK ──────────│ 서버: "새 연결이네!" → 불필요한 연결 생성 X
↑ 클라이언트는 이 연결을 모름
서버 자원 낭비 (Half-Open)
3-way로 해결
[ 3-way handshake ]
지연된 SYN이 도착해도:
│── SYN (원본, 뒤늦게 도착) ─→ │
│ ←── SYN+ACK ─────── │ 서버: SYN_RCVD
│ │
│ (클라이언트는 이 연결을 요청한 적 없음)
│ → ACK를 보내지 않음 (또는 RST 전송)
│ │
│ │ 서버: ACK 안 옴 → 타임아웃 → 연결 폐기
↑ 불필요한 연결 생성 방지 O
정리:
2-way: 서버가 일방적으로 연결 수립 → 유령 연결 발생 가능
3-way: 클라이언트의 최종 확인(ACK)이 있어야 연결 확정 → 안전
3-way가 확인하는 것
Step 1 (SYN):
클라이언트 → 서버 전송 가능 확인 O
Step 2 (SYN-ACK):
서버 → 클라이언트 전송 가능 확인 O
서버가 클라이언트의 데이터를 수신 가능 O
Step 3 (ACK):
클라이언트가 서버의 데이터를 수신 가능 O
→ 양방향 통신 가능 확인 완료
클라이언트 송신 서버 송신 클라이언트 수신 서버 수신
Step 1 (SYN) O
Step 2 (SYN-ACK) O O
Step 3 (ACK) O
→ 3단계면 4가지를 모두 확인 가능
→ 이론적 최소 횟수가 3회
TCP 헤더 플래그 상세
TCP 헤더 (20바이트)
┌────────────────────────────────────────┐
│ 출발지 포트 (16비트) │ 목적지 포트 (16비트) │
├────────────────────────────────────────┤
│ Sequence Number (32비트) │
├────────────────────────────────────────┤
│ Acknowledgment Number (32비트) │
├────────────────────────────────────────┤
│ 헤더길이│예약│ 제어 플래그 │ 윈도우 크기 │
│ │ │URG ACK PSH RST SYN FIN│ │
├────────────────────────────────────────┤
│ 체크섬 │ 긴급 포인터 │
└────────────────────────────────────────┘
| 플래그 | 의미 | 3-way에서 사용 |
| SYN | 동기화, 연결 시작 | Step 1, 2 |
| ACK | 확인 응답 | Step 2, 3 |
| FIN | 연결 종료 | 4-way handshake에서 사용 |
| RST | 연결 강제 리셋 | 비정상 종료 시 |
| PSH | 즉시 전달 | 데이터 전송 시 |
| URG | 긴급 데이터 | 긴급 데이터 전송 시 |
3-way handshake에서 발생할 수 있는 문제
1. SYN Flood 공격
공격자가 대량의 SYN을 보내고 ACK는 보내지 않음
공격자 ── SYN ──→ 서버: SYN_RCVD (자원 할당)
공격자 ── SYN ──→ 서버: SYN_RCVD (자원 할당)
공격자 ── SYN ──→ 서버: SYN_RCVD (자원 할당)
... 수만 개 ...
서버: SYN_RCVD 상태가 가득 참 (Backlog Queue 포화)
→ 정상 사용자의 연결 요청 거부 (DoS)
| 대응 방법 | 설명 |
| SYN Cookie | SYN_RCVD 상태를 저장하지 않고 seq에 정보를 인코딩 |
| Backlog Queue 증가 | 대기열 크기 확대 |
| SYN 타임아웃 단축 | SYN_RCVD 대기 시간 줄임 |
| 방화벽/IDS | 비정상 SYN 패턴 차단 |
2. ACK 유실
클라이언트 서버
│── SYN ──────→ │
│ ←── SYN+ACK ─── │
│── ACK ──────→ (유실!) │
│ │
(ESTABLISHED) (SYN_RCVD)
│ 타이머 동작 중...
│── 데이터 전송 ──→ │
│ 서버: "데이터에 ACK가 포함되어 있네"
│ → ESTABLISHED로 전환 O
Step 3의 ACK가 유실되더라도 클라이언트가 데이터를 보내면 그 패킷의 ACK 번호로 연결이 확인됩니다.
연결 수립 후 → 데이터 전송 → 연결 종료
[ 3-way handshake ] → [ 데이터 전송 ] → [ 4-way handshake ]
연결 수립 통신 연결 종료
클라이언트 서버 클라이언트 서버
SYN ────→ FIN ────→
←──── SYN+ACK ←──── ACK
ACK ────→ ←──── FIN
ACK ────→
ESTABLISHED ESTABLISHED CLOSED CLOSED
핵심 정리
3-way handshake는 SYN → SYN-ACK → ACK 세 단계로 TCP 연결을 수립하는 과정입니다. 양방향 통신 가능 여부를 확인하고, 서로의 ISN(초기 시퀀스 번호)을 교환하며, 2-way로는 방지할 수 없는 유령 연결 문제를 해결합니다. 이론적으로 양방향 4가지 확인(송신·수신 × 2)을 3단계에 담는 최소 횟수이며, SYN Flood 같은 공격에는 SYN Cookie 등으로 대응합니다.