TCP 3-Way Handshake는 어떻게 연결을 만드는가
빠른 답
- 3-way handshake는 양쪽이 서로 송수신 가능하다는 사실과 초기 순서 번호를 확인하는 연결 설정 절차입니다.
- SYN은 연결 요청, SYN-ACK은 요청 수락과 서버의 초기 번호 전달, ACK는 클라이언트의 최종 확인입니다.
- 연결이 안 될 때는 애플리케이션 코드보다 먼저 포트 리슨 상태, 방화벽, 라우팅, SYN 재전송 여부를 확인해야 합니다.
- tcpdump나 Wireshark 출력에서 SYN만 반복되면 서버 응답이 없거나 중간 네트워크에서 막힌 상황일 가능성이 큽니다.
목차
시간 흐름으로 이해하기
흐름으로 보기
이 흐름은 브라우저, API 클라이언트, 백엔드 서비스, 데이터베이스 클라이언트 모두에서 같은 구조로 나타납니다. 차이가 있다면 80, 443, 5432, 6379처럼 대상 포트와 그 위에서 동작하는 애플리케이션 프로토콜이 달라진다는 점입니다.
ESTABLISHED 상태가 되었다는 말은 TCP 연결이 만들어졌다는 뜻입니다. TLS 인증서 검증, HTTP 라우팅, 데이터베이스 인증, 애플리케이션 권한 검사는 그 이후의 단계입니다. 그래서 장애를 볼 때는 “TCP 연결이 안 되는가”와 “연결 이후 요청 처리가 실패하는가”를 분리해서 보는 것이 좋습니다.
TCP 연결 설정이 필요한 이유
TCP는 UDP처럼 데이터를 바로 보내는 방식이 아니라 연결을 먼저 만들고 그 위에서 바이트 스트림을 주고받습니다. 이 연결은 단순히 서버가 살아 있다는 신호가 아닙니다. 양쪽이 서로 패킷을 주고받을 수 있는지, 이후 데이터의 순서를 어떤 번호부터 계산할지, 수신 버퍼는 어느 정도인지 같은 조건을 맞추는 과정입니다.
TCP는 데이터 순서 보장, 손실 복구, 중복 제거, 흐름 제어를 제공합니다. 이런 기능을 위해서는 “상대가 내 첫 패킷을 받았는지”와 “상대가 보낸 첫 번호를 내가 확인했는지”가 필요합니다. 3-way handshake의 세 단계는 이 확인을 최소한의 메시지 교환으로 수행합니다.
운영 관점에서는 handshake가 애플리케이션 앞단의 경계선 역할을 합니다. 예를 들어 HTTP 500은 애플리케이션 내부 예외일 가능성이 있지만, SYN이 반복되고 SYN-ACK이 돌아오지 않는다면 서버 프로세스, 포트 바인딩, 보안 그룹, OS 방화벽, 라우팅 같은 네트워크 구간을 먼저 살펴봐야 합니다.
Sequence Number와 ACK Number
Sequence Number는 내가 보내는 바이트 스트림이 어느 번호에서 시작하는지를 나타냅니다. 클라이언트와 서버는 연결마다 각자의 초기 순서 번호를 고릅니다. 이 번호는 이후 데이터 재전송, 순서 보정, 중복 감지의 기준이 됩니다.
ACK Number는 마지막으로 받은 번호가 아니라 다음에 받을 것으로 기대하는 번호입니다. SYN은 실제 데이터가 없어도 순서 번호 공간을 1만큼 사용합니다. 그래서 서버는 클라이언트의 SYN을 받으면 ACK = 클라이언트 초기 순서 번호 + 1로 응답합니다. 클라이언트도 서버의 SYN에 대해 같은 방식으로 ACK = 서버 초기 순서 번호 + 1을 보냅니다.
Client -> Server: SYN, seq=1000
Server -> Client: SYN-ACK, seq=7000, ack=1001
Client -> Server: ACK, seq=1001, ack=7001
이 예시에서 클라이언트의 초기 번호가 1000이면 서버는 1001을 기대한다고 응답합니다. 서버의 초기 번호가 7000이면 클라이언트는 7001을 기대한다고 확인합니다. 패킷 캡처에서 숫자가 +1로 움직이는 이유가 여기에 있습니다.
3-way handshake가 끝났다고 애플리케이션 요청까지 성공한 것은 아닙니다. TCP 연결은 만들어졌지만 TLS handshake에서 인증서 오류가 날 수 있고, HTTP 요청은 404나 500을 받을 수 있습니다. 반대로 애플리케이션 로그가 전혀 남지 않는다면 요청이 애플리케이션 레이어까지 오기 전에 끊겼을 가능성을 확인해 볼 만합니다.
서버 포트와 배포 구성
TCP 연결이 만들어지려면 서버 프로세스가 대상 IP와 포트에서 리슨하고 있어야 합니다. 0.0.0.0:8080에 바인딩하면 외부 인터페이스로 들어오는 8080 포트 연결을 받을 수 있습니다. 반면 127.0.0.1:8080에만 바인딩하면 같은 서버 내부에서는 접속되지만 외부 호스트에서는 접속할 수 없습니다.
리버스 프록시나 로드 밸런서를 앞에 두면 TCP 연결 지점이 하나 더 생깁니다. 클라이언트는 로드 밸런서의 443 포트에 연결하고, 로드 밸런서는 내부 애플리케이션 서버의 8080 포트로 다시 연결할 수 있습니다. 이때 외부 방화벽은 443을 허용해야 하고, 내부 보안 그룹이나 OS 방화벽은 로드 밸런서에서 애플리케이션 서버로 가는 8080을 허용해야 합니다.
아래 구성은 외부 HTTP 요청을 내부 애플리케이션 포트로 넘기는 간단한 예입니다.
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
이 구성에서는 클라이언트와 Nginx 사이에 한 번, Nginx와 애플리케이션 서버 사이에 또 한 번 TCP handshake가 일어납니다. 외부에서는 접속되는데 애플리케이션 로그가 비어 있다면 프록시 이후 구간의 upstream 주소, 포트, 로컬 바인딩, 내부 방화벽을 따로 확인해야 합니다.
연결 확인을 위한 최소 예시
가장 작은 단위로 TCP 연결을 확인하려면 특정 포트에서 리슨하는 서버를 띄운 뒤 클라이언트에서 접속해 볼 수 있습니다. 아래 Python 코드는 8080 포트에서 TCP 연결을 받고 짧은 응답을 보냅니다.
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("0.0.0.0", 8080))
server.listen(5)
print("listening on 0.0.0.0:8080")
while True:
conn, addr = server.accept()
print("accepted", addr)
conn.sendall(b"hello\n")
conn.close()
서버를 실행한 뒤 다른 터미널에서 접속하면 TCP 연결 가능 여부를 확인할 수 있습니다. nc는 애플리케이션 프로토콜을 깊게 타지 않고 포트 접근성을 확인할 때 유용합니다.
$ ss -lntp | grep ':8080'
LISTEN 0 128 0.0.0.0:8080 0.0.0.0:* users:(("python",pid=2418,fd=3))
$ nc -vz 127.0.0.1 8080
Connection to 127.0.0.1 8080 port [tcp/http-alt] succeeded!
$ nc 127.0.0.1 8080
hello
이 출력은 해당 IP와 포트까지 TCP 연결이 가능하다는 뜻입니다. 다만 HTTP API가 정상이라는 의미는 아닙니다. TCP 포트 확인과 애플리케이션 응답 확인은 서로 다른 검사입니다.
패킷 캡처로 보는 정상 handshake
패킷 캡처는 TCP handshake를 직접 확인할 수 있는 방법입니다. 서버나 클라이언트에서 tcpdump를 실행한 뒤 접속을 시도하면 SYN, SYN-ACK, ACK 순서가 보입니다.
$ sudo tcpdump -nn -i eth0 'tcp port 8080'
10:15:01.120001 IP 10.0.1.25.51544 > 10.0.2.10.8080: Flags [S], seq 1000, win 64240
10:15:01.120420 IP 10.0.2.10.8080 > 10.0.1.25.51544: Flags [S.], seq 7000, ack 1001, win 65160
10:15:01.120731 IP 10.0.1.25.51544 > 10.0.2.10.8080: Flags [.], ack 7001, win 64240
첫 줄은 클라이언트가 서버 8080 포트로 SYN을 보낸 장면입니다. 둘째 줄은 서버가 SYN-ACK로 응답한 장면입니다. 셋째 줄은 클라이언트가 최종 ACK를 보낸 장면입니다. 세 줄이 짧은 시간 안에 이어진다면 TCP 연결 설정 자체는 성공한 것으로 볼 수 있습니다.
애플리케이션 로그에는 요청이 없는데 패킷 캡처에서 handshake가 보인다면 연결 이후 단계를 확인해야 합니다. 예를 들어 TLS를 기대하는 포트에 평문 HTTP를 보내고 있거나, 프록시의 upstream 경로가 잘못되었거나, 애플리케이션이 연결 직후 요청을 닫고 있을 수 있습니다.
장애 징후와 점검 순서
연결 실패 메시지는 비슷해 보여도 의미가 다릅니다. Connection refused는 대상 호스트까지 도달했지만 해당 포트에서 받아 주는 프로세스가 없거나 중간 장비가 거절 응답을 보낸 경우에 자주 나타납니다.
Connection timed out은 SYN을 보냈지만 응답을 받지 못했을 때 많이 보입니다. 방화벽, 보안 그룹, 네트워크 ACL, 라우팅, 서버 다운 상태처럼 응답이 돌아오지 않는 구간을 확인해야 합니다.
No route to host는 목적지로 가는 경로가 없거나 네트워크 계층에서 도달할 수 없다는 신호에 가깝습니다. 이 경우에는 애플리케이션 설정보다 라우팅 테이블, 서브넷, VPN, 피어링, 보안 정책을 먼저 보는 편이 원인에 더 가깝습니다.
$ nc -vz 10.0.2.10 8080
nc: connect to 10.0.2.10 port 8080 (tcp) failed: Connection refused
$ nc -vz -w 3 10.0.2.10 8080
nc: connect to 10.0.2.10 port 8080 (tcp) timed out: Operation now in progress
$ sudo tcpdump -nn 'host 10.0.2.10 and tcp port 8080'
10:21:33.001 IP 10.0.1.25.51610 > 10.0.2.10.8080: Flags [S], seq 392812, win 64240
10:21:34.026 IP 10.0.1.25.51610 > 10.0.2.10.8080: Flags [S], seq 392812, win 64240
10:21:36.074 IP 10.0.1.25.51610 > 10.0.2.10.8080: Flags [S], seq 392812, win 64240
마지막 캡처처럼 같은 seq의 SYN이 반복되고 SYN-ACK가 없다면 클라이언트의 요청이 서버에 도달하지 못했거나, 서버 응답이 클라이언트로 돌아오지 못하는 상황을 의심할 수 있습니다.
운영 점검은 다음 순서로 나누면 범위를 줄이기 쉽습니다.
- DNS가 기대한 IP로 해석되는지 확인합니다.
- 서버 프로세스가 실행 중인지 확인합니다.
- 서버가 올바른 IP와 포트에 리슨하는지 확인합니다.
- 서버 로컬에서
127.0.0.1또는 내부 IP로 접속해 봅니다. - 같은 내부망의 다른 호스트에서 접속해 봅니다.
- 클라이언트와 서버 양쪽에서 패킷을 캡처해
SYN과SYN-ACK의 위치를 비교합니다. - 로드 밸런서, 보안 그룹, OS 방화벽, 라우팅 테이블을 연결 경로 순서대로 확인합니다.
- TCP 연결은 성공하지만 요청이 실패한다면 TLS, Host 헤더, 프록시 upstream, 애플리케이션 로그를 확인합니다.
흔한 오해
ping이 된다고 TCP 연결까지 된다고 볼 수는 없습니다. ping은 ICMP를 사용하고, TCP 포트 리슨 여부와 직접 연결되지 않습니다. 어떤 환경은 ICMP를 막아 ping은 실패하지만 TCP 443은 열려 있을 수 있습니다. 반대로 ping은 되지만 TCP 8080은 방화벽에 막혀 있을 수도 있습니다.
TCP handshake 성공도 서비스 정상 동작을 보장하지 않습니다. 연결은 만들어졌지만 TLS 인증서가 만료되었을 수 있고, HTTP 라우팅이 맞지 않을 수 있으며, 데이터베이스 인증이나 애플리케이션 권한 검사에서 실패할 수 있습니다.
문제를 나눠 볼 때는 계층을 분리하는 편이 도움이 됩니다. 먼저 목적지 IP까지 경로가 있는지 확인하고, 다음으로 대상 TCP 포트가 열려 있는지 봅니다. 그다음 SYN, SYN-ACK, ACK가 오가는지 확인합니다. 이후 TLS handshake, HTTP 응답, 데이터베이스 프로토콜 응답, 애플리케이션 로그를 차례로 이어 보면 “서버가 안 된다”는 넓은 문제를 더 작은 단서로 바꿀 수 있습니다.
원문 참고
https://www.maeil-mail.kr/question/76
댓글
댓글 쓰기