infra
Platform

모듈 맵

[Linux] 네트워크 트러블슈팅 심화 — tcpdump와 패킷 분석

0 / 20 완료

펼치기

리눅스 서버 운영 · 20 / 20

[Linux] 네트워크 트러블슈팅 심화 — tcpdump와 패킷 분석

ss/netstat 소켓 상태 분석과 tcpdump 패킷 캡처로 실제 네트워크 장애를 레이어별로 진단합니다

🚨INCIDENT ALERT
HIGH

오후 2시, "API가 갑자기 안 된다"는 슬랙 메시지가 날아옵니다. 서버는 살아 있고 애플리케이션 로그에도 에러가 없습니다. 그런데 클라이언트에서는 계속 타임아웃입니다. ping은 되는데 실제 포트로 연결이 안 되고, 어느 구간에서 패킷이 사라지는지 알 수 없습니다. 이런 상황에서 ss로 소켓 상태를 읽고 tcpdump로 실제 패킷을 잡아볼 수 있다면 10분 안에 어디가 문제인지 특정할 수 있습니다.

네트워크 트러블슈팅 심화 — tcpdump와 패킷 분석

서버 로그에는 아무것도 없는데 연결이 안 된다. 애플리케이션은 멀쩡한데 응답이 느리다. 이런 상황에서 필요한 건 실제 패킷을 들여다보는 능력입니다. ss로 소켓 상태를 읽고, tcpdump로 패킷을 잡아서 무슨 일이 일어나는지 직접 확인하는 것이 이 모듈의 목표입니다.


이번 챕터에서 배울 것

네트워크 문제는 "안 된다"는 증상 하나로 시작합니다. 계층을 내려가면서 어디서 막혔는지 좁혀가는 것이 핵심입니다.

  • 1TCP 상태 머신(SYN_SENT/ESTABLISHED/TIME_WAIT)을 읽고 무슨 일이 일어나는지 판단할 수 있다
  • 2ss -tnp로 소켓 상태와 Recv-Q/Send-Q, 프로세스를 매핑해 분석할 수 있다
  • 3tcpdump 필터 문법으로 패킷을 실시간 캡처할 수 있다
  • 4tcpdump 결과를 Wireshark 파일로 저장해 심층 분석할 수 있다
  • 5레이어별 진단 플로우로 어디서 막혔는지 5분 안에 특정할 수 있다
  • 6SYN 타임아웃·TIME_WAIT 고갈·비대칭 라우팅 같은 실제 장애 패턴을 식별할 수 있다
실습 환경 준비

💡개념

TCP 상태 머신 — ss 출력으로 지금 무슨 일이 일어나는지 읽기

로컬 터미널
# 실행 중인 TCP 연결 전체 확인 (프로세스 포함)
ss -tnp

# LISTEN 중인 포트만 확인
ss -tnlp

# 특정 포트 필터
ss -tnp '( dport = :443 or sport = :443 )'
State     Recv-Q  Send-Q  Local Address:Port  Peer Address:Port  Process
LISTEN         0     128  0.0.0.0:80          0.0.0.0:*          nginx
ESTABLISHED    0       0  10.0.0.1:80         10.0.0.5:54321     nginx
SYN-SENT       0       1  10.0.0.1:45123      10.0.0.9:5432      python
TIME_WAIT      0       0  10.0.0.1:80         10.0.0.7:39201     -

상태별 진단 의미:

상태의미다량 발생 시 의심
SYN-SENT연결 시도 중, 응답 대기대상 서버 방화벽 차단 또는 프로세스 없음
SYN-RECV서버가 SYN 받음, ACK 대기SYN Flood 공격 가능성
ESTABLISHED정상 연결과다 시 Connection Pool 미사용
TIME_WAIT종료 후 대기(2MSL)단기 연결 반복 → 포트 고갈 위험
CLOSE-WAIT원격이 먼저 종료했는데 로컬이 안 닫음애플리케이션 코드 버그 (소켓 미닫힘)

Recv-Q / Send-Q 읽기:

로컬 터미널
# LISTEN 소켓의 Recv-Q = 수락 대기 중인 연결 큐 크기
# ESTABLISHED 소켓의 Recv-Q = 앱이 아직 읽지 않은 데이터 (바이트)
# ESTABLISHED 소켓의 Send-Q = ACK 못 받은 전송 대기 데이터 (바이트)

# Recv-Q 지속적으로 높음 → 앱 처리 속도 부족 (백프레셔)
# Send-Q 지속적으로 높음 → 네트워크 혼잡 또는 수신 측 처리 지연

💡개념

tcpdump — 패킷을 직접 잡아서 보기

로컬 터미널
# 기본 문법
tcpdump [옵션] [필터 표현식]

# 주요 옵션
-i eth0      # 인터페이스 지정 (-i any: 모든 인터페이스)
-n           # DNS 역조회 비활성화 (속도 향상)
-v/-vv       # 상세 출력 (패킷 헤더 포함)
-c 100       # 패킷 100개 캡처 후 종료
-w dump.pcap # 파일로 저장 (Wireshark로 분석 가능)
-r dump.pcap # 저장된 파일 읽기

# 자주 쓰는 필터 조합
sudo tcpdump -i eth0 -n 'tcp port 80'
sudo tcpdump -i eth0 -n 'host 192.168.1.10'
sudo tcpdump -i eth0 -n 'tcp port 5432 and host 10.0.0.9'
sudo tcpdump -i eth0 -n 'tcp[tcpflags] & (tcp-syn) != 0'  # SYN 패킷만

출력 읽기:

14:23:01.123456 IP 10.0.0.5.54321 > 10.0.0.1.80: Flags [S], seq 1234567
14:23:01.123478 IP 10.0.0.1.80 > 10.0.0.5.54321: Flags [S.], seq 9876543, ack 1234568
14:23:01.123490 IP 10.0.0.5.54321 > 10.0.0.1.80: Flags [.], ack 9876544
Flags의미
[S]SYN (연결 시작)
[S.]SYN-ACK (연결 응답)
[.]ACK
[P.]PSH+ACK (데이터 전송)
[F.]FIN (연결 종료 시작)
[R]RST (연결 강제 초기화)

RST가 보이면: 서버가 연결 요청을 거부했거나, 이미 닫힌 포트에 패킷이 도달한 것입니다.


레이어별 진단 플로우

진단의 핵심은 아래 계층부터 위로 좁혀가는 것입니다. L2가 막혀 있으면 L7을 아무리 봐도 소용이 없습니다. 각 계층에서 무엇을 확인하고 OK/NG를 어떻게 가르는지 한눈에 봅니다.

OSI 계층별 진단 순서 — L2 링크부터 L7 응용까지 위로 좁혀가는 흐름확대

같은 흐름을 명령어 의사결정 트리로 정리하면 다음과 같습니다.

"연결이 안 된다"
    │
    ├─ ping 대상서버IP
    │   ├─ 실패 → L3 문제 (라우팅, 방화벽 ICMP 차단)
    │   └─ 성공 ↓
    │
    ├─ telnet 대상IP 포트  (또는 nc -zv 대상IP 포트)
    │   ├─ 실패 → L4 문제 → 아래 확인
    │   │   ├─ 대상서버: ss -tnlp | grep <포트>  (프로세스 LISTEN 중인가?)
    │   │   └─ 대상서버: iptables -L -n | grep <포트>  (방화벽 차단인가?)
    │   └─ 성공 ↓
    │
    ├─ curl -v http://대상IP  (또는 애플리케이션 직접 요청)
    │   ├─ 실패 → L7 문제 (앱 설정, 인증, SSL)
    │   └─ 느림 → tcpdump로 RTT 분석
    │
    └─ tcpdump -i eth0 -n 'host 대상IP and tcp port <포트>'
        ├─ SYN 후 응답 없음 → 방화벽이 패킷 DROP
        ├─ SYN → RST → 포트 닫힘 또는 프로세스 없음
        └─ 정상 handshake 후 느림 → 앱 레이어 문제

패킷이 클라이언트에서 서버 앱까지 가는 경로의 각 지점에서 무엇을 캡처하고 어떻게 막힌 곳을 특정하는지는 아래 그림으로 정리됩니다.

패킷 경로 추적 — 클라이언트부터 서버 앱까지 구간별 확인 포인트와 결론확대


실습 단계
1

TCP 소켓 상태 전체 확인 (상태별 분류)

ss -tnp | awk '{print $1}' | sort | uniq -c | sort -rn

예상 출력

     42 ESTABLISHED
      5 TIME_WAIT
      1 State
(상태별 개수 — TIME_WAIT가 수백 개면 포트 고갈 위험)
2

특정 포트 LISTEN 여부 및 담당 프로세스 확인

sudo ss -tnlp | grep ':80\|:443\|:22'

예상 출력

LISTEN  0  128  0.0.0.0:22   0.0.0.0:*  users:(("sshd",pid=1234,fd=3))
LISTEN  0  128  0.0.0.0:80   0.0.0.0:*  users:(("nginx",pid=5678,fd=6))
3

Recv-Q/Send-Q 이상 소켓 탐지

ss -tnp | awk 'NR>1 && ($2+0 > 0 || $3+0 > 0) {print}'

예상 출력

(정상 시 출력 없음. 출력이 있으면 해당 소켓 버퍼에 데이터 적체 — 처리 지연 또는 네트워크 혼잡)
4

대상 포트 연결 가능 여부 테스트

nc -zv 127.0.0.1 22 && echo 'OK' || echo 'FAILED'

예상 출력

Connection to 127.0.0.1 22 port [tcp/ssh] succeeded!
OK
5

tcpdump로 로컬 22번 포트 패킷 10개 캡처

sudo tcpdump -i lo -c 10 -n 'tcp port 22' & sleep 1 && ssh -o StrictHostKeyChecking=no localhost true 2>/dev/null; wait

예상 출력

14:00:00.000001 IP 127.0.0.1.PORT > 127.0.0.1.22: Flags [S], ...
14:00:00.000010 IP 127.0.0.1.22 > 127.0.0.1.PORT: Flags [S.], ...
10 packets captured
🔍실행 후 확인할 것
  • ss -tnp 출력에서 먼저 State 컬럼을 보고 비정상 상태(SYN-SENT, CLOSE-WAIT)를 찾고, 그 다음 Recv-Q/Send-Q 값이 0이 아닌 행을 확인 — Recv-Q 높으면 앱 처리 지연, Send-Q 높으면 네트워크 혼잡
  • 연결 상태 기준: TIME_WAIT 100개 미만=정상, 500개 이상=포트 고갈 주의, CLOSE-WAIT 10개 이상=소켓 누수 버그 의심 — ss -tnp | grep -c CLOSE_WAIT 로 즉시 확인
  • nc -zv 성공인데 실제 요청 타임아웃이면 → L7 앱 문제(프로세스 LISTEN은 하지만 처리 안 함), tcpdump에서 SYN 후 응답 없으면 → 방화벽 DROP(REJECT는 즉시 RST 반환)을 의미

SYN 타임아웃 — 방화벽이 패킷을 DROP하는 경우

로컬 터미널
# 증상: nc -zv 10.0.0.9 5432 → Connection timed out (즉시 Refused 아님)
# "timed out"은 패킷이 사라졌다는 뜻. "refused"는 RST를 받았다는 뜻.

# 제어 노드에서 tcpdump (SYN 패킷이 전송되는지 확인)
sudo tcpdump -i eth0 -n 'host 10.0.0.9 and tcp port 5432'

# → SYN 보냄 [S] 보이지만 [S.] 응답 없음 → 패킷이 대상 서버에 안 도달하거나 DROP됨

# 대상 서버에서 확인
sudo tcpdump -i any -n 'tcp port 5432'  # 패킷이 도달하는가?
sudo iptables -L INPUT -n -v | grep 5432  # 방화벽 DROP 규칙 있는가?
sudo ss -tnlp | grep 5432                 # 프로세스가 LISTEN 중인가?

방화벽 DROP vs REJECT 차이:

  • DROP: 패킷을 조용히 버림 → 클라이언트는 타임아웃까지 기다림
  • REJECT: RST 응답을 즉시 보냄 → 클라이언트는 즉시 "Connection refused" 받음

소켓 누수 — 애플리케이션이 연결을 닫지 않는 버그

로컬 터미널
# 증상 확인
ss -tnp | grep CLOSE-WAIT | wc -l   # 개수 확인
ss -tnp | grep CLOSE-WAIT           # 어떤 프로세스인지 확인

# CLOSE-WAIT의 의미:
# 원격 피어가 FIN을 보내 먼저 연결 종료를 요청했지만
# 로컬 애플리케이션이 소켓을 close()하지 않아 연결이 남아있는 상태

# 원인: 데이터베이스/HTTP 클라이언트에서 connection.close() 누락
# 특히 예외 처리 경로(try/except)에서 close가 호출되지 않는 케이스

# 임시 조치: 해당 프로세스 재시작
# 근본 조치: 코드에서 finally: conn.close() 또는 with 구문 사용 확인

💼
실무 맥락
현업 패턴

실무에서 tcpdump가 쓰이는 순간

API 간헐적 지연 — "로그는 정상인데 왜 느리지?" 케이스:

로컬 터미널
# 1단계: 어느 구간에서 지연이 발생하는지 tcpdump로 확인
sudo tcpdump -i eth0 -n -w /tmp/api-trace.pcap 'tcp port 8080' &
# API 요청 재현
kill %1

# 2단계: RTT 계산 (SYN → SYN-ACK 시간이 정상인가?)
tcpdump -r /tmp/api-trace.pcap -n -tt | head -20

# 3단계: Wireshark에서 pcap 파일 열어 TCP Stream Follow
# → "Time between request and response" 확인

# 자주 발견되는 패턴:
# SYN → SYN-ACK: 0.001ms (네트워크는 정상)
# Request → Response: 800ms (앱 처리 지연)
# → DB 쿼리 슬로우 로그 확인으로 전환

온콜 대응 체크리스트:

1. ss -tnp | wc -l              → 연결 수 급증?
2. ss -tnp | grep -c TIME_WAIT → TIME_WAIT 고갈?
3. ss -tnp | grep -c CLOSE_WAIT → 소켓 누수?
4. ss -tnlp                    → 포트 정상 LISTEN?
5. tcpdump -i any -c 50 -n ... → 패킷 실제 도달?

이 모듈로 Linux Master 트랙의 모든 20개 과정을 완료했습니다. 파일시스템부터 네트워크 트러블슈팅까지, 리눅스 인프라 엔지니어로서 필요한 핵심 역량을 모두 다뤘습니다.

지식 확인

퀴즈 — 4문제

Q1

마이크로서비스 A에서 서비스 B의 8080 포트로 연결하는 코드를 배포했는데 타임아웃이 납니다. 서비스 A 서버에서 ss -tnp를 실행하니 서비스 B 주소로 향하는 소켓이 모두 SYN-SENT 상태입니다. 원인으로 가장 가능성 높은 것은?

Q2

외부 API를 많이 호출하는 서버에서 'Cannot assign requested address: connect failed' 오류가 간헐적으로 발생합니다. ss -tnp 출력을 보니 TIME_WAIT 소켓이 30,000개가 넘습니다. 이 상황의 원인과 올바른 해결 방법은?

Q3

Nginx 리버스 프록시와 백엔드 API 서버(192.168.1.20:3000) 사이 통신 문제를 tcpdump로 캡처하려 합니다. Nginx 서버에서 실행할 올바른 명령어는?

Q4

애플리케이션 로그에는 에러가 없는데 특정 API 엔드포인트 응답이 간헐적으로 5~10초 지연됩니다. 로드밸런서 지표도 정상입니다. 네트워크 레이어 문제를 진단하기 위해 가장 먼저 확인해야 할 것은?

0 / 4 답변