TCP 연결을 시작할 때 세 번의 패킷 교환으로 서로의 준비 상태를 확인하는 절차.
무엇을 주고받나
클라이언트 서버
│ │
│──── SYN (seq=x) ───────────► │ "나 x번부터 보낼게"
│ │
│◄─── SYN+ACK ─────────────────│ "받았어(ack=x+1). 나는 y번부터"
│ (seq=y, ack=x+1) │
│ │
│──── ACK (ack=y+1) ─────────► │ "네 번호도 받았어"
│ │
연결 성립 연결 성립
- SYN — Synchronize. "시퀀스 번호를 맞추자"는 플래그
- seq — 내가 쓸 시작 번호(ISN, Initial Sequence Number)
- ack=x+1 — "x번까지 잘 받았으니 다음은 x+1을 기대한다"
왜 두 번으로는 안 되는가 — 핵심
연결은 양방향이다. 각자의 시작 번호를 상대가 확실히 알았는지까지 확인해야 한다.
- 1번 (SYN) — : 서버가 클라의 번호를 앎
- 2번 (SYN+ACK) : 클라가 "서버가 내 번호를 알았음"을 앎 + 서버 번호를 앎
- 3번 (ACK) — : 서버가 "클라가 내 번호를 알았음"을 앎 ← 이게 빠지면 미완
두 번에서 멈추면 서버는 자기 번호가 전달됐는지 모른다. 그 상태로 데이터를 보내면 클라가 못 받았을 수 있다.
종료는 왜 4번인가
클라 ──── FIN ────► 서버 "나는 다 보냈어"
클라 ◄─── ACK ───── 서버 "알겠어" (아직 내 데이터는 남았을 수 있음)
클라 ◄─── FIN ───── 서버 "나도 다 보냈어"
클라 ──── ACK ────► 서버 "알겠어"
연결 수립은 "시작하자"에 양쪽이 동의하면 되지만, 종료는 각 방향을 따로 닫는다. 서버가 아직 보낼 데이터가 남아 있을 수 있어 ACK와 FIN을 합칠 수 없다. (보낼 게 없으면 합쳐져 3번으로 끝나기도 한다.)
꼬리질문
- "ISN을 난수로 두는 이유는?" → ① 이전 연결의 늦은 패킷이 새 연결에 섞이는 것 방지 ② 시퀀스 번호를 예측해 끼어드는 세션 하이재킹 공격 방지.
- "SYN Flood 공격이 뭔가?" → SYN만 잔뜩 보내고 3번째 ACK를 안 보낸다. 서버는 반쯤 열린 연결을 큐에 쌓아 두다 고갈된다. SYN 쿠키로 방어한다 (연결 정보를 서버가 저장하지 않고 시퀀스 번호에 인코딩해 되돌려 받는다).