네트워크 디버깅 (2) - 패킷 분석 - soo:bak
작성일 :
패킷 레벨에서 무엇을 볼 수 있는가
Part 1에서 ping, traceroute, nslookup 등 계층별 진단 도구를 살펴보았습니다. ping은 상대방이 살아 있는지, traceroute는 경로 어디에서 패킷이 멈추는지, nslookup은 DNS 응답이 올바른지를 알려주었습니다.
하지만 이 도구들이 보여주는 것은 각 계층의 상태 요약입니다. “연결이 되는가”, “경로가 어디서 끊기는가” 같은 큰 질문에는 답할 수 있지만, 더 깊은 질문 앞에서는 멈춥니다. “TCP 핸드셰이크가 왜 실패하는가”, “TLS 인증서 교환에서 무엇이 잘못되었는가”, “재전송이 발생하는 구간이 어디인가”처럼 프로토콜 내부 동작을 확인해야 하는 상황에서는 진단 도구의 요약만으로 원인을 특정할 수 없습니다.
이런 상황에서 필요한 것은 네트워크를 지나는 데이터 자체를 직접 확인하는 것입니다. 로그가 아닌, 실제로 회선을 오간 바이트를 보는 것입니다.
패킷 캡처(Packet Capture)는 NIC를 통과하는 프레임을 바이트 단위로 기록하는 기법입니다. 애플리케이션 로그가 “연결 실패”라고만 알려줄 때, 패킷 캡처는 SYN은 보냈는데 SYN-ACK가 오지 않았다는 사실까지 보여줍니다. 로그는 이미 해석된 결과이지만, 패킷은 실제로 일어난 일 그 자체입니다.
이 글에서는 패킷 캡처의 원리(Promiscuous 모드, BPF)부터, tcpdump와 Wireshark의 실전 활용, TCP와 TLS 수준의 문제 분석, 그리고 실제 장애 사례까지 다룹니다.
패킷 캡처의 원리
Promiscuous 모드
일반적으로 NIC(Network Interface Card)는 이더넷 프레임의 목적지 MAC 주소가 자신의 MAC 주소와 일치할 때만 그 프레임을 운영체제로 올려보냅니다. 나머지 프레임은 NIC 수준에서 폐기됩니다.
패킷을 캡처하려면 이 필터링을 꺼야 합니다. Promiscuous 모드는 목적지 MAC 주소와 관계없이, 수신되는 모든 프레임을 운영체제로 전달하도록 NIC를 설정합니다. tcpdump나 Wireshark가 캡처를 시작하면 자동으로 이 모드를 활성화합니다.
1
2
3
4
5
6
일반 모드:
NIC → 내 MAC 주소인가? → 예 → 처리
→ 아니오 → 폐기
Promiscuous 모드:
NIC → 모든 프레임을 운영체제로 전달 (캡처)
다만 현대 네트워크는 대부분 스위치 기반입니다. 스위치는 각 포트에 해당 장비의 트래픽만 전달하므로, Promiscuous 모드를 켜더라도 자신에게 오는 패킷만 보입니다.
다른 장비의 트래픽까지 캡처하려면 스위치에서 포트 미러링(Port Mirroring)을 설정해야 합니다. 포트 미러링은 특정 포트의 트래픽을 캡처용 포트로 복제하는 기능입니다.
BPF (Berkeley Packet Filter)
Promiscuous 모드에서는 NIC가 모든 프레임을 운영체제의 커널로 올려보냅니다. tcpdump 같은 캡처 도구는 커널이 아닌 사용자 공간(user space)에서 실행됩니다. 커널 공간과 사용자 공간은 메모리 영역이 분리되어 있어, 패킷 하나를 전달할 때마다 CPU가 모드를 전환하고 데이터를 복사해야 합니다.
트래픽이 많은 네트워크에서는 초당 수만 개의 패킷이 올라올 수 있고, 이를 전부 사용자 공간까지 복사하면 시스템 부하가 급격히 증가합니다.
BPF(Berkeley Packet Filter)는 이 문제를 해결합니다. tcpdump가 tcp port 80 같은 필터를 지정하면, BPF는 그 조건을 바이트코드로 변환하여 커널에서 직접 실행합니다. 조건에 맞지 않는 패킷은 커널에서 바로 버리고, 조건에 맞는 패킷만 사용자 공간으로 올려보냅니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌─────────────────┐
│ 사용자 공간 │
│ (tcpdump) │
└────────┬────────┘
│
│ 필터 조건에 맞는 패킷만
│
┌────────┴────────┐
│ BPF │ ← 커널에서 필터링
│ (필터 실행) │
└────────┬────────┘
│
│ 모든 패킷
│
┌────────┴────────┐
│ NIC │
└─────────────────┘
예를 들어 초당 10,000개의 패킷이 NIC에 도착하더라도, tcp port 80 필터가 적용되면 HTTP 트래픽에 해당하는 수백 개만 사용자 공간으로 전달됩니다.
캡처 위치의 중요성
같은 문제라도 캡처 위치에 따라 보이는 패킷이 완전히 다릅니다.
1
2
3
4
5
클라이언트 ─────── 방화벽 ─────── 서버
클라이언트에서 캡처: 나가는 패킷 O, 방화벽에서 차단되면 응답 X
서버에서 캡처: 방화벽 통과한 패킷만 보임
방화벽에서 캡처: 차단된 패킷도 볼 수 있음
클라이언트에서 캡처하면 SYN 패킷이 나가는 것은 보이지만, 방화벽이 그 패킷을 DROP했다면 SYN-ACK가 돌아오지 않습니다. 타임아웃만 관찰됩니다. 같은 상황을 방화벽에서 캡처하면 SYN이 도착했지만 DROP 규칙에 의해 폐기된 사실을 확인할 수 있습니다. 서버에서 캡처하면 아예 패킷이 보이지 않습니다.
문제의 원인이 어느 구간에 있는지 모를 때는, 양쪽 끝(클라이언트와 서버)에서 동시에 캡처하여 비교합니다. 한쪽에서 보낸 패킷이 다른 쪽에 보이지 않으면, 그 사이 경로에서 패킷이 유실되고 있는 것입니다.
tcpdump 활용
tcpdump는 커맨드라인 기반의 패킷 캡처 도구입니다. GUI 없이 SSH로 원격 서버에 접속한 상황에서도 사용할 수 있어, 서버 측 네트워크 문제를 진단할 때 가장 먼저 꺼내는 도구입니다.
기본 사용법
tcpdump의 기본 명령은 인터페이스(-i)를 지정하여 해당 인터페이스의 모든 패킷을 캡처합니다. 실행하면 앞서 설명한 Promiscuous 모드와 BPF 필터가 자동으로 적용됩니다. sudo가 필요한 이유는 NIC를 Promiscuous 모드로 전환하려면 루트 권한이 필요하기 때문입니다.
1
2
3
4
5
6
7
8
9
10
11
# 기본 캡처 (모든 패킷)
sudo tcpdump -i eth0
# 패킷 내용까지 (hex + ASCII)
sudo tcpdump -i eth0 -X
# 파일로 저장 (.pcap은 패킷 캡처 데이터의 표준 파일 형식)
sudo tcpdump -i eth0 -w capture.pcap
# 저장된 파일 읽기
tcpdump -r capture.pcap
필터 문법
tcpdump의 필터는 BPF 문법을 사용합니다. 호스트, 포트, 프로토콜을 조합하여 원하는 트래픽만 캡처할 수 있습니다. and, or 연산자로 조건을 조합하고, 복잡한 필터는 따옴표로 감싸야 셸이 올바르게 해석합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 호스트 필터
tcpdump host 192.168.1.10
tcpdump src host 192.168.1.10
tcpdump dst host 192.168.1.10
# 네트워크 필터
tcpdump net 192.168.1.0/24
# 포트 필터
tcpdump port 80
tcpdump port 80 or port 443
tcpdump portrange 8000-8080
# 프로토콜 필터
tcpdump tcp
tcpdump udp
tcpdump icmp
# 조합
tcpdump "host 192.168.1.10 and port 80"
tcpdump "src host 192.168.1.10 and dst port 443"
자주 쓰는 필터
앞의 필터 문법을 조합하면 실무에서 자주 쓰는 패턴을 만들 수 있습니다. 특히 TCP 플래그를 직접 지정하면 연결 시도(SYN)나 비정상 종료(RST)만 골라 볼 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# HTTP 트래픽
tcpdump -i eth0 "tcp port 80"
# HTTPS 트래픽
tcpdump -i eth0 "tcp port 443"
# DNS 쿼리
tcpdump -i eth0 "udp port 53"
# TCP SYN 패킷만 (연결 시도)
tcpdump -i eth0 "tcp[tcpflags] & tcp-syn != 0"
# TCP RST 패킷만 (연결 거부/리셋)
tcpdump -i eth0 "tcp[tcpflags] & tcp-rst != 0"
# ICMP (ping)
tcpdump -i eth0 icmp
출력 해석
tcpdump의 출력은 한 줄에 패킷 하나를 표시합니다. 다음은 포트 80에 대한 캡처 예시입니다.
1
2
3
tcpdump -i eth0 -n port 80
# 22:31:01.123456 IP 192.168.1.10.54321 > 93.184.216.34.80: Flags [S], seq 123456789, win 65535, options [mss 1460,sackOK,TS val 123 ecr 0,nop,wscale 7], length 0
출력을 왼쪽부터 읽어봅니다. 22:31:01.123456은 마이크로초 단위 타임스탬프입니다. 192.168.1.10.54321은 출발지 IP와 포트이고, > 뒤의 93.184.216.34.80은 목적지 IP와 포트를 나타냅니다.
Flags [S]는 TCP 플래그로, 이 패킷이 SYN(연결 시도)임을 뜻합니다. seq는 시퀀스 번호, win은 수신 윈도우 크기, length는 페이로드 길이입니다.
TCP 플래그는 패킷의 목적을 나타냅니다. tcpdump에서 대괄호 안에 약어로 표시되며, 각 약어의 의미는 다음과 같습니다.
S— SYN (연결 시도).— ACK (확인 응답)F— FIN (정상 종료 요청)R— RST (연결 강제 종료)P— PSH (데이터 즉시 전달)
여러 플래그가 동시에 설정되면 함께 표시됩니다. 예를 들어, [S.]는 SYN+ACK를 뜻합니다.
정상적인 TCP 3-way handshake는 [S] → [S.] → [.] 순서로 나타납니다. 정상 종료는 [F.]로 시작됩니다. [R]이나 [R.]이 보이면 비정상 종료(RST)이므로 원인 조사가 필요합니다.
Wireshark 활용
GUI 기반 분석
tcpdump가 텍스트 기반의 빠른 캡처에 적합하다면, Wireshark는 캡처된 데이터의 심층 분석에 강점이 있습니다. 패킷의 각 계층(이더넷 → IP → TCP → HTTP)을 트리 구조로 펼쳐 각 필드의 값을 확인할 수 있습니다. HTTP, DNS, TLS 등 수백 개의 프로토콜을 자동으로 해석하여 사람이 읽기 쉬운 형태로 보여주며, 재전송률과 지연 시간 분포를 시각화하는 통계 그래프도 제공합니다.
일반적인 작업 흐름은 서버에서 tcpdump로 캡처하여 .pcap 파일로 저장한 뒤, 해당 파일을 로컬 Wireshark에서 여는 방식입니다.
캡처 필터 vs 디스플레이 필터
Wireshark는 두 종류의 필터를 사용하며, 적용 시점이 다릅니다.
캡처 필터는 tcpdump와 동일한 BPF 문법을 사용합니다. 즉, 앞에서 다룬 tcpdump 필터 표현식을 그대로 쓸 수 있습니다. 패킷을 저장하는 시점에 적용되어, host 192.168.1.10 and port 80처럼 지정하면 조건에 맞는 패킷만 .pcap 파일에 기록됩니다. 디스크와 메모리를 절약할 수 있지만, 캡처 범위를 너무 좁게 잡으면 분석에 필요한 패킷을 놓칠 수 있고, 한 번 놓친 패킷은 복구할 수 없습니다.
디스플레이 필터는 이미 캡처된 데이터 안에서 조건에 맞는 패킷만 화면에 표시합니다. Wireshark 고유 문법을 사용하며, http.request.method == "GET"처럼 프로토콜 필드 단위로 필터링합니다. 캡처 데이터는 그대로 유지되므로 분석 시점에 자유롭게 조건을 바꿀 수 있습니다.
유용한 디스플레이 필터
디스플레이 필터는 프로토콜.필드 연산자 값 형식으로 작성합니다. 자주 사용하는 필터를 상황별로 정리하면 다음과 같습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# IP 주소
ip.addr == 192.168.1.10
ip.src == 192.168.1.10
ip.dst == 192.168.1.10
# 포트
tcp.port == 80
tcp.srcport == 443
# HTTP
http
http.request
http.response
http.request.method == "POST"
http.response.code == 404
# DNS
dns
dns.qry.name == "example.com"
# TLS
tls
tls.handshake
tls.handshake.type == 1 # Client Hello
# TCP 문제
tcp.analysis.retransmission
tcp.analysis.duplicate_ack
tcp.analysis.zero_window
tcp.flags.reset == 1
# 특정 대화
tcp.stream eq 5
Follow Stream
패킷 목록에서 개별 패킷을 하나씩 보면 전체 대화의 흐름을 파악하기 어렵습니다. Follow Stream 기능은 하나의 TCP 연결에 속하는 모든 패킷을 시간순으로 조합합니다. 클라이언트가 보낸 데이터와 서버가 보낸 데이터를 대화 형태로 보여줍니다.
1
2
3
4
5
6
7
8
9
10
Right-click → Follow → TCP Stream
HTTP 요청/응답이 대화 형태로 표시됨:
GET /index.html HTTP/1.1 ← 클라이언트 (빨간색)
Host: example.com
...
HTTP/1.1 200 OK ← 서버 (파란색)
Content-Type: text/html
...
HTTP처럼 텍스트 기반 프로토콜에서는 요청과 응답의 전체 내용을 읽을 수 있어, 헤더 값이나 응답 코드를 바로 확인할 수 있습니다.
TCP 문제 분석
tcpdump와 Wireshark로 패킷을 캡처했다면, 다음 단계는 TCP 수준에서 문제를 식별하는 것입니다. Wireshark는 TCP의 동작을 분석하여 재전송, 윈도우 문제, 비정상 종료 같은 패턴을 자동으로 탐지하고 표시합니다.
재전송 패턴
TCP는 데이터를 보낸 뒤 ACK가 일정 시간 내에 돌아오지 않으면 해당 세그먼트를 재전송합니다. 재전송이 발생하면 Wireshark는 해당 패킷을 [TCP Retransmission]으로 표시하고, 검정 또는 빨간색 배경으로 강조합니다.
1
2
3
4
패킷 흐름:
1. [TCP] seq=1, len=1000 → (전송)
2. [TCP] seq=1001, len=1000 → (전송)
3. [TCP] seq=1, len=1000 → (재전송! 1번에 대한 ACK 미수신)
간헐적인 재전송은 정상적인 네트워크에서도 발생합니다. 다만 전체 패킷 대비 재전송 패킷의 비율이 1%를 넘으면 네트워크 혼잡이나 경로상 패킷 손실을 의심해야 합니다. Wireshark의 tcp.analysis.retransmission 필터로 재전송 패킷만 골라낼 수 있습니다.
재전송의 분포 패턴도 원인 파악에 도움이 됩니다. 특정 시간대에 집중되면 그 시간대의 트래픽 급증이 원인일 가능성이 높고, 전체 구간에 고르게 분포하면 물리적 회선이나 경로상 장비의 문제를 의심합니다.
윈도우 스케일링
TCP 헤더의 윈도우 크기 필드는 16비트로, 최대값이 65,535바이트(약 64KB)입니다. 이 값은 수신자가 ACK를 보내지 않고 한 번에 받을 수 있는 데이터의 양을 뜻합니다. 송신자는 이 크기만큼 보낸 뒤 ACK가 올 때까지 기다려야 합니다.
1Gbps 회선에서 RTT가 100ms인 경우를 생각해봅니다. 64KB를 보내고 100ms를 기다리면, 초당 전송량은 64KB ÷ 0.1초 = 640KB/s, 즉 약 5Mbps입니다. 1Gbps 회선의 0.5%밖에 활용하지 못하는 것입니다.
대역폭을 충분히 쓰려면 윈도우가 훨씬 커야 합니다.
윈도우 스케일링(Window Scaling)은 TCP 3-way handshake 시 양쪽이 협상하는 옵션입니다. 스케일 팩터를 정하면, 이후 패킷의 윈도우 값에 이 팩터를 곱한 것이 실제 윈도우 크기가 됩니다.
1
2
3
4
5
# TCP 옵션에서 확인
Options: ... Window scale: 7 (multiply by 128)
실제 윈도우 = 헤더의 윈도우 값 × 128
예: 윈도우 값 512 → 실제 윈도우 65,536바이트
패킷 캡처에서 성능 문제를 분석할 때는, 3-way handshake 패킷의 TCP 옵션에서 Window scale 값을 확인합니다. 이 옵션이 없으면 양쪽 중 하나가 윈도우 스케일링을 지원하지 않는 것이고, 64KB 제한이 그대로 적용되어 대역폭을 충분히 활용하지 못하고 있을 수 있습니다.
윈도우 크기와 관련된 또 다른 성능 문제로 Zero Window가 있습니다. 윈도우 스케일링이 윈도우의 최대 크기를 늘리는 것이라면, Zero Window는 수신자의 윈도우가 0으로 줄어드는 상황입니다. 수신자의 애플리케이션이 데이터를 빠르게 처리하지 못하면 수신 버퍼가 가득 차서 윈도우 크기가 0이 됩니다. Wireshark에서 tcp.analysis.zero_window 필터로 이 상황을 탐지할 수 있으며, 수신 측 애플리케이션의 처리 속도가 병목인지 확인하는 데 유용합니다.
RST 원인 분석
패킷 캡처에서 RST 패킷을 발견하면, 어딘가에서 연결이 비정상적으로 끊겼다는 뜻입니다. TCP RST(Reset)는 연결을 강제로 종료하는 패킷입니다. 정상적인 연결 종료(FIN 교환)와 달리, RST는 즉시 연결을 끊고 송수신 버퍼의 데이터를 버립니다.
RST의 원인은 발생 시점에 따라 달라집니다.
- 연결 초기(SYN 직후) — 대상 포트에서 리슨하는 프로세스가 없거나, 방화벽이 REJECT 정책으로 RST를 보내는 것입니다.
- 데이터 전송 중 — 애플리케이션이 비정상 종료되었거나, 로드밸런서 같은 중간 장비가 연결을 끊은 것입니다.
- 유휴 상태 지속 후 — 방화벽이나 NAT 장비의 세션 타임아웃을 의심합니다.
1
2
3
4
RST 발생 시점별 원인:
SYN → RST : 포트 닫힘 또는 방화벽 REJECT
데이터 전송 → RST : 애플리케이션 비정상 종료 또는 중간 장비
유휴 상태 → RST : 방화벽/NAT 세션 타임아웃
RST 패킷의 출발지 IP를 확인하면 어느 장비가 연결을 끊었는지 알 수 있습니다. 서버 IP에서 RST가 왔다면 서버 측 문제이고, 중간 IP에서 왔다면 방화벽이나 로드밸런서가 개입한 것입니다.
TLS 트래픽 분석
앞에서 TCP 수준의 재전송, 윈도우, RST 문제를 분석하는 방법을 살펴봤습니다. TCP 연결이 정상이더라도 그 위의 TLS 계층에서 문제가 발생할 수 있으며, HTTPS가 기본이 된 현대 웹에서는 이 경우가 흔합니다.
핸드셰이크 확인
TLS로 암호화된 애플리케이션 데이터는 패킷 캡처로 볼 수 없습니다. 하지만 TLS 핸드셰이크의 초반 메시지는 암호화 전에 교환되므로, 패킷 캡처에서 확인할 수 있습니다. TLS 1.2에서는 핸드셰이크 전체가 평문이고, TLS 1.3에서는 Client Hello와 Server Hello까지는 평문이지만 그 이후 메시지(Certificate 포함)는 암호화됩니다.
Wireshark에서 tls.handshake 필터를 적용하면 핸드셰이크 패킷만 골라낼 수 있습니다. Client Hello에는 클라이언트가 지원하는 TLS 버전, 암호 스위트(cipher suite, 암호화 알고리즘 조합) 목록, SNI(Server Name Indication, 접속하려는 도메인 이름)가 포함되어 있습니다. Server Hello에는 서버가 선택한 TLS 버전과 암호 스위트가 담겨 있고, 이어서 Certificate 메시지로 서버 인증서가 전달됩니다.
1
2
3
4
5
Client Hello → 지원 TLS 버전, 암호 스위트 목록, SNI
Server Hello ← 선택된 TLS 버전, 선택된 암호 스위트
Certificate ← 서버 인증서 (체인 포함)
Key Exchange ↔ 키 교환 데이터
Finished ↔ 핸드셰이크 완료, 이후 데이터 암호화 시작
TLS 연결이 실패하는 경우, Client Hello와 Server Hello의 TLS 버전이나 암호 스위트가 불일치하는지, Alert 메시지가 어느 시점에 발생하는지를 확인합니다.
인증서 문제 확인
Wireshark에서 Certificate 메시지를 펼치면 서버가 보낸 인증서 체인을 확인할 수 있습니다. 서버는 보통 서버 인증서와 중간 CA 인증서를 함께 보냅니다. 루트 CA 인증서는 운영체제나 브라우저에 미리 설치된 신뢰 저장소(trust store)에 들어 있으므로, 서버가 별도로 보내지 않아도 클라이언트가 검증할 수 있습니다.
인증서 관련 TLS 연결 실패는 대부분 세 가지 원인에서 비롯됩니다.
- 인증서 만료 — Not After 필드가 현재 시각보다 이전이면 클라이언트는 연결을 거부합니다.
- 도메인 불일치 — 인증서의 CN(Common Name)이나 SAN(Subject Alternative Name)에 접속하려는 도메인이 포함되어 있지 않으면 연결이 거부됩니다.
- 체인 불완전 — 중간 CA 인증서가 누락되어 클라이언트가 인증서 체인을 루트 CA까지 검증하지 못하는 경우입니다.
Wireshark에서 인증서의 유효기간과 도메인 필드를 직접 확인하면 어떤 원인인지 특정할 수 있습니다.
SSLKEYLOGFILE
TLS 핸드셰이크는 평문으로 볼 수 있지만, 핸드셰이크 이후의 애플리케이션 데이터는 암호화되어 내용을 확인할 수 없습니다. 개발 환경에서 암호화된 트래픽의 내용까지 분석해야 한다면, TLS 세션 키를 파일로 내보내는 방법이 있습니다.
SSLKEYLOGFILE 환경 변수를 설정하면, Chrome, Firefox, curl 등의 클라이언트가 TLS 세션의 비밀 값을 지정된 파일에 기록합니다. 기록되는 값은 TLS 1.2의 pre-master secret 또는 TLS 1.3의 핸드셰이크/애플리케이션 트래픽 시크릿입니다. Wireshark에 이 파일을 지정하면 해당 세션의 암호화된 데이터를 복호화하여 볼 수 있습니다.
1
2
3
4
5
6
7
8
9
# 환경 변수 설정
export SSLKEYLOGFILE=/tmp/keys.log
# 브라우저 또는 curl 실행
curl https://example.com
# Wireshark에서:
# Edit → Preferences → Protocols → TLS
# (Pre)-Master-Secret log filename: /tmp/keys.log
이 방법은 OpenSSL, BoringSSL, NSS 등 SSLKEYLOGFILE을 지원하는 TLS 라이브러리를 사용하는 클라이언트에서만 동작합니다. Java(JSSE)나 자체 TLS 구현을 사용하는 애플리케이션에서는 SSLKEYLOGFILE을 인식하지 않으므로, 별도의 방법이 필요합니다.
이 파일에는 TLS 세션의 암호화 키가 그대로 담기므로, 개발 및 디버깅 환경에서만 사용해야 합니다. 프로덕션 환경에서 이 변수가 설정되어 있으면 모든 TLS 트래픽이 복호화 가능해지는 보안 위험이 됩니다.
실제 사례
앞에서 살펴본 도구와 분석 기법을 실제 상황에 적용해 봅니다. 세 가지 사례 모두 같은 접근법을 따릅니다. 증상을 관찰하고, 패킷을 캡처하고, 패턴을 식별하고, 원인을 특정합니다.
연결 실패
증상: curl http://example.com이 타임아웃됩니다.
tcpdump로 해당 호스트와 포트의 트래픽을 캡처합니다.
1
2
3
4
5
6
7
tcpdump -i eth0 "host example.com and port 80"
# 결과:
# 22:31:01 IP 192.168.1.10.54321 > 93.184.216.34.80: Flags [S]
# 22:31:02 IP 192.168.1.10.54321 > 93.184.216.34.80: Flags [S] (재전송, 1초 후)
# 22:31:04 IP 192.168.1.10.54321 > 93.184.216.34.80: Flags [S] (재전송, 2초 후)
# (응답 없음)
SYN 패킷이 3번 나갔지만 SYN-ACK가 한 번도 돌아오지 않았습니다. RST가 돌아왔다면 포트가 닫혀 있다는 뜻이지만, 아무 응답이 없는 것은 다른 문제를 가리킵니다. 패킷이 목적지까지 도달하지 못했거나, 도달했지만 응답이 돌아오지 못한 것입니다.
이런 증상의 대표적인 원인은 방화벽의 DROP 정책입니다. DROP은 RST조차 보내지 않으므로 클라이언트 입장에서는 타임아웃만 관찰됩니다. 서버가 완전히 다운되었거나 라우팅 설정이 잘못되어 패킷이 엉뚱한 곳으로 향하는 경우에도 같은 증상이 나타납니다.
느린 응답
증상: 웹 페이지 로딩이 느립니다. 연결 자체는 성공하지만 페이지가 표시될 때까지 수 초가 걸립니다.
Wireshark로 캡처한 뒤, 시간순으로 각 단계의 소요 시간을 측정합니다.
1
2
3
4
5
6
단계별 소요 시간:
DNS 응답: 10ms ← 정상
TCP 핸드셰이크: 30ms ← 정상
TLS 핸드셰이크: 100ms ← 정상
HTTP 요청 전송: 즉시 ← 정상
HTTP 응답 시작: 3,000ms ← 문제
DNS, TCP, TLS까지는 모두 정상 범위입니다. 문제는 HTTP 요청을 보낸 시점부터 첫 번째 응답 바이트가 도착할 때까지 3초가 걸린다는 점입니다. 이 구간을 TTFB(Time To First Byte)라고 합니다.
TTFB는 서버가 요청을 처리하는 데 걸리는 시간을 반영합니다. 네트워크 구간(DNS, TCP, TLS)은 정상이므로, 서버 측 애플리케이션 로직이나 데이터베이스 쿼리에서 병목이 발생하고 있는 것입니다. 원인을 네트워크가 아닌 서버 측에서 찾아야 합니다.
간헐적 문제
증상: 서비스가 대부분 정상이지만, 15분에 한 번꼴로 연결이 끊깁니다.
간헐적 문제는 장시간 캡처가 필요합니다. tcpdump -w capture.pcap으로 수 시간 동안 캡처한 뒤, Wireshark에서 tcp.flags.reset == 1 필터로 RST 패킷만 골라냅니다.
1
2
3
4
RST 패킷의 시간 패턴:
22:30:45: [TCP RST] ← 마지막 데이터로부터 900초 후
22:45:12: [TCP RST] ← 마지막 데이터로부터 900초 후
23:00:38: [TCP RST] ← 마지막 데이터로부터 900초 후
RST가 발생하기 직전의 패킷을 확인하면, 마지막 데이터 패킷과 RST 사이에 약 900초(15분)의 유휴 구간이 있습니다. 세 번 모두 같은 간격입니다. 경로상의 방화벽이나 NAT 장비가 900초 동안 트래픽이 없는 세션을 타임아웃으로 정리하고 있는 것입니다.
해결 방법은 유휴 구간이 타임아웃 임계값에 도달하지 않도록 만드는 것입니다. TCP Keep-alive는 연결이 유휴 상태일 때 주기적으로 빈 패킷을 보내 세션이 살아 있음을 알리는 기능입니다. Keep-alive 간격을 900초 미만(예: 300초)으로 설정하면, 타임아웃 전에 패킷이 오가므로 세션이 유지됩니다.
마무리
이 글에서는 패킷 캡처와 분석 도구를 살펴보았습니다. 캡처 위치에 따라 보이는 패킷이 다르므로, 클라이언트, 서버, 방화벽 중 문제에 가장 가까운 지점에서 캡처하는 것이 중요합니다. tcpdump의 BPF 필터로 관련 트래픽만 추출하고, Wireshark의 디스플레이 필터로 캡처된 데이터를 정밀하게 분석합니다.
TCP 플래그, 시퀀스 번호, 타임스탬프를 시간순으로 따라가면 프로토콜의 동작을 직접 확인할 수 있습니다. 재전송, RST, Zero Window 같은 비정상 패턴은 네트워크 혼잡, 연결 거부, 수신 버퍼 포화 같은 구체적 원인을 가리킵니다.
애플리케이션 로그는 이미 해석된 정보지만, 패킷은 실제로 일어난 일 그 자체입니다.
관련 글
시리즈
- 네트워크 디버깅 (1) - 계층별 진단 도구
- 네트워크 디버깅 (2) - 패킷 분석 (현재 글)