46. 시퀀스 번호·ACK·윈도우 — TCP가 데이터를 빠짐없이 전달하는 방법

changseop lee·6일 전

📚 네트워크 · 패킷 분석 › 01. TCP/IP 구조 이해 — 46편
이전 글: 45. TCP 연결 종료 — FIN과 RST의 차이 · 다음 글: 47. DNS 이름 해석 흐름 — 도메인이 IP가 되기까지
참고(리눅스 시스템 기초): IP / TCP / UDP / Port — TCP·UDP 기본 개념과 포트

1. 왜 알아야 하는가

앞선 글에서 TCP 연결이 3-Way Handshake로 성립하고, FIN·RST로 끝나며, 그 사이에 여러 상태를 거친다는 것을 확인했습니다. 그런데 연결이 "성립했다"는 것과 "데이터가 실제로 얼마나, 제대로 오갔다"는 것은 다른 이야기입니다.

TCP가 순서 보장과 손실 복구를 하는 근거는 헤더의 세 값, 시퀀스 번호(Sequence Number), 확인 응답 번호(Acknowledgment Number), 윈도우(Window) 입니다. 이 값을 읽을 수 있으면 다음 질문에 패킷 근거로 답할 수 있습니다.

  • 이 세션에서 실제로 몇 바이트가 전달되었는가
  • 중간에 손실·재전송이 있었는가, 있었다면 어느 쪽에서 막혔는가
  • 수신 측이 처리를 못 해 전송이 멈춘 것인가(Zero Window), 네트워크가 느린 것인가
  • 세션 중간에 끼어든 패킷(RST 등)이 정상 흐름과 맞는 번호를 가졌는가

관제 업무에서 "연결은 됐는데 데이터가 안 갔다", "업로드가 중간에 멈췄다", "IPS 적용 후 특정 서비스가 느려졌다" 같은 문의가 들어오면, 결국 이 세 값의 흐름을 확인하게 됩니다.


2. 핵심 개념

2-1. 시퀀스 번호 — "내가 보내는 바이트의 위치"

시퀀스 번호는 이 세그먼트에 담긴 첫 번째 데이터 바이트의 번호입니다. TCP는 데이터를 패킷 단위가 아니라 바이트 스트림으로 보고 바이트마다 번호를 매깁니다.

  • 연결 시작 시 양쪽은 각자 초기 시퀀스 번호(ISN) 를 무작위에 가깝게 정합니다. 예측 가능한 ISN은 과거 세션 위조 공격에 악용되었기 때문에 현대 OS는 추측하기 어렵게 생성합니다.
  • SYN과 FIN은 데이터가 없어도 시퀀스 번호를 1 소비합니다. 그래서 Handshake 직후 첫 데이터는 ISN+1부터 시작합니다.
  • 방향마다 번호가 따로 있습니다. 클라이언트→서버 번호와 서버→클라이언트 번호는 서로 무관합니다.

2-2. ACK 번호 — "다음에 받을 바이트 번호"

ACK 번호는 "여기까지 잘 받았으니, 다음엔 이 번호부터 보내 달라" 는 뜻입니다. 즉 마지막으로 받은 바이트 번호 + 1입니다.

  • TCP의 기본 ACK는 누적(Cumulative) ACK 입니다. ACK=5001이면 5000번 바이트까지 빈틈없이 받았다는 의미입니다.
  • 중간이 빠지면 수신 측은 뒤쪽 데이터를 받아도 같은 ACK 번호를 반복해서 보냅니다. 이것이 중복 ACK(Duplicate ACK) 입니다.
  • 이를 보완하는 SACK(Selective ACK) 옵션은 "빠진 구간 뒤의 이 범위는 받았다"를 알려 줍니다. SACK은 SYN 단계에서 양쪽이 지원을 알린 경우에만 사용됩니다.

2-3. 윈도우 — "지금 더 받을 수 있는 양"

윈도우 필드는 수신 측이 ACK 번호 이후로 추가로 받을 수 있는 바이트 수(수신 윈도우, rwnd)를 광고합니다. 송신 측은 ACK를 받지 못한 데이터가 이 크기를 넘지 않도록 전송량을 조절합니다. 이것이 흐름 제어(Flow Control) 입니다.

  • 헤더의 윈도우 필드는 16비트라 최대 65,535입니다. 고속망에서는 부족하기 때문에 Window Scale 옵션으로 값을 왼쪽 시프트해서 씁니다. 실제 윈도우 = 필드 값 × 2^(scale).
  • Window Scale은 SYN/SYN-ACK에서만 협상됩니다. 캡처에 Handshake가 없으면 실제 윈도우 크기를 계산할 수 없습니다.
  • 윈도우가 0이 되면(Zero Window) 송신 측은 데이터를 멈추고, 주기적으로 Window Probe를 보내 공간이 생겼는지 확인합니다.

2-4. 수신 윈도우와 혼잡 윈도우

구분수신 윈도우 (rwnd)혼잡 윈도우 (cwnd)
누가 정하나수신 측송신 측 (내부 계산)
무엇을 보호하나수신 측 버퍼네트워크 경로
패킷에 보이나보임 (Window 필드)보이지 않음 (ss -ti 로 호스트에서 확인)
줄어드는 원인애플리케이션이 데이터를 늦게 읽음손실·지연 감지

송신 측이 한 번에 보낼 수 있는 양은 대략 min(rwnd, cwnd) 입니다. 패킷에서 윈도우가 넉넉한데도 전송이 느리다면 혼잡 제어나 경로 문제를 의심할 수 있습니다.


3. 동작 원리

Wireshark와 tcpdump는 보기 쉽게 ISN을 0으로 놓은 상대 시퀀스 번호를 기본으로 보여 줍니다. 아래 흐름도도 상대 번호 기준입니다.

시퀀스·ACK 재전송 시퀀스
그림 1. 중간 세그먼트가 유실되면 수신 측은 같은 ACK를 반복하고, 송신 측은 빠진 부분을 재전송합니다

 Client (192.168.10.20)                          Server (192.168.10.10:8080)
   |  SYN  seq=0                         win=64240  (WS=128, SACK_PERM, MSS=1460)
   |------------------------------------------------------------->|
   |  SYN,ACK  seq=0 ack=1               win=65160  (WS=128, SACK_PERM, MSS=1460)
   |<-------------------------------------------------------------|
   |  ACK  seq=1 ack=1                                            |
   |------------------------------------------------------------->|
   |  PSH,ACK  seq=1 ack=1  len=100   (HTTP 요청 100바이트)        |
   |------------------------------------------------------------->|
   |                     ↓ 서버: 1~100번 바이트 수신 → 다음은 101  |
   |  ACK  seq=1 ack=101                                          |
   |<-------------------------------------------------------------|
   |  ACK  seq=1 ack=101  len=1448   (응답 1번째 조각: 1~1448)     |
   |<-------------------------------------------------------------|
   |  ACK  seq=1449 ack=101 len=1448 (응답 2번째 조각) ✕ 손실       |
   |<- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -|
   |  ACK  seq=2897 ack=101 len=1448 (3번째 조각 도착)              |
   |<-------------------------------------------------------------|
   |                     ↓ 클라이언트: 1449부터 비어 있음           |
   |  ACK  seq=101 ack=1449  SACK=2897-4345   (Dup ACK)           |
   |------------------------------------------------------------->|
   |  ACK  seq=1449 ack=101 len=1448 (재전송)                      |
   |<-------------------------------------------------------------|
   |  ACK  seq=101 ack=4345   (빈틈 복구 → 누적 ACK 한 번에 전진)    |
   |------------------------------------------------------------->|

핵심 규칙은 세 가지입니다.

  1. 다음 seq = 현재 seq + 데이터 길이(len) (SYN·FIN은 +1)
  2. ACK는 상대방 기준 다음에 받을 번호 — 내 seq가 아니라 상대 seq에 대응합니다.
  3. ACK가 전진하지 않고 반복되면 수신 측에 빈틈이 있다는 신호입니다.

3-1. 재전송이 일어나는 두 가지 경로

방식계기패킷에서 보이는 모습
타임아웃 재전송 (RTO)일정 시간 ACK가 오지 않음같은 seq 세그먼트가 시간 간격을 두고 다시 나옴, 간격이 점점 늘어남
빠른 재전송 (Fast Retransmit)중복 ACK 3개 수신Dup ACK 여러 개 직후 같은 seq 재전송

RTO 재전송은 간격이 지수적으로 늘어나기 때문에(백오프), 응답이 전혀 없는 상대에게 보낸 SYN이나 데이터가 1초, 2초, 4초… 간격으로 반복되는 모습이 보입니다. 이 값은 OS와 설정마다 다르므로 정확한 초 단위보다는 "간격이 늘어나는 패턴"으로 기억하는 것이 좋습니다.

3-2. Zero Window 흐름

Server(송신) ── data ──→ Client(수신)   win 점점 감소 (애플리케이션이 읽지 않음)
      ↓
Client ── ACK win=0 ──→ Server          [TCP ZeroWindow]
      ↓
Server ── Window Probe ──→ Client       (주기적으로 1바이트 또는 빈 세그먼트)
      ↓
Client ── ACK win=29200 ──→ Server      [TCP Window Update]
      ↓
Server ── data 재개

Zero Window는 네트워크 문제가 아니라 수신 측 애플리케이션이 버퍼를 비우지 못하는 상황입니다. 원인을 네트워크 장비에서 찾으면 헛수고가 됩니다.


4. 실제 명령어 / 실습

실습 환경: 본인 소유 Linux VM (Rocky Linux 또는 Ubuntu), 인터페이스 이름은 ens33 예시입니다. VM(192.168.10.10)을 서버로, 같은 대역의 다른 VM 또는 호스트 PC(192.168.10.20)를 클라이언트로 사용합니다.

4-1. 도구 설치

# Rocky Linux
sudo dnf install -y tcpdump iproute python3 curl

# Ubuntu
sudo apt update && sudo apt install -y tcpdump iproute2 python3 curl

4-2. 관찰용 트래픽 만들기

# 서버 VM: 20MB 테스트 파일 생성 후 간이 웹 서버 실행
mkdir -p ~/seqlab && cd ~/seqlab
dd if=/dev/zero of=test.bin bs=1M count=20
python3 -m http.server 8080

# 방화벽 임시 허용 (실습 후 반드시 제거)
# Rocky:  sudo firewall-cmd --add-port=8080/tcp        (--permanent 없이 → 재부팅/리로드 시 사라짐)
# Ubuntu: sudo ufw allow 8080/tcp   → 실습 후 sudo ufw delete allow 8080/tcp
# 서버 VM, 다른 터미널: 캡처 시작 (Handshake부터 반드시 포함)
sudo tcpdump -i ens33 -nn port 8080 -w seq_lab.pcap

# 클라이언트: 파일 다운로드
curl -o /dev/null http://192.168.10.10:8080/test.bin

4-3. 상대 번호와 절대 번호 비교

# 상대 시퀀스 번호 (기본)
sudo tcpdump -nn -r seq_lab.pcap -c 8

# 절대 시퀀스 번호 (-S): 실제 헤더에 들어 있는 값
sudo tcpdump -nn -S -r seq_lab.pcap -c 8

📷 [실습 화면 삽입] 같은 pcap을 -S 없이/있이 읽은 두 결과를 나란히 — seq 값이 0·1 기준 상대 번호와 큰 절대 번호로 다르게 보이는 부분

4-4. 호스트에서 연결 내부 상태 보기

# 다운로드가 진행 중일 때 서버 VM에서 실행
ss -tin '( sport = :8080 )'

# 커널의 TCP 관련 설정
sysctl net.ipv4.tcp_window_scaling net.ipv4.tcp_sack net.ipv4.tcp_rmem net.ipv4.tcp_wmem

# 시스템 전체 재전송 카운터 (누적값)
nstat -az TcpRetransSegs TcpOutSegs

4-5. (선택) 손실을 인위적으로 만들어 재전송 관찰

본인 VM에서만, SSH 세션도 영향을 받을 수 있으니 콘솔 접속 상태에서 진행합니다.

# Rocky는 tc가 iproute-tc 패키지, netem 모듈은 kernel-modules-extra 에 있을 수 있음
#   sudo dnf install -y iproute-tc kernel-modules-extra
# Ubuntu는 iproute2 에 tc 포함

sudo tc qdisc add dev ens33 root netem loss 3%   # 송신 패킷 3% 손실
# ... 클라이언트에서 curl 다운로드 재실행, tcpdump로 캡처 ...
sudo tc qdisc del dev ens33 root                 # 반드시 원복

📷 [실습 화면 삽입] netem 적용 후 Wireshark에서 tcp.analysis.flags 필터를 건 화면 — Dup ACK와 Retransmission 표시 줄


5. 결과 확인

ss -tin 출력 형식 예시(값은 환경마다 다름):

ESTAB 0  2896000  192.168.10.10:8080  192.168.10.20:51544
	 cubic wscale:7,7 rto:204 rtt:0.61/0.12 ato:40 mss:1448 pmtu:1500
	 rcvmss:536 advmss:1448 cwnd:10 bytes_sent:8452096 bytes_acked:5556096
	 retrans:0/3 ...
필드의미볼 점
wscale:7,7송신/수신 Window Scale 값Handshake에서 협상된 결과
rto현재 재전송 타임아웃(ms)손실이 이어지면 커짐
rtt평균 왕복 시간/편차(ms)경로 지연 판단
mss세그먼트 최대 데이터 크기1460에서 TCP 옵션(타임스탬프 등)만큼 줄어든 값일 수 있음
cwnd혼잡 윈도우(세그먼트 수)패킷에는 없는, 송신 측 내부 값
retrans현재 미해결/누적 재전송 수0이 아니면 손실 경험

tcpdump 상대 번호 출력 형식 예시(값은 환경마다 다름):

192.168.10.20.51544 > 192.168.10.10.8080: Flags [P.], seq 1:95, ack 1, win 502, length 94
192.168.10.10.8080 > 192.168.10.20.51544: Flags [.], ack 95, win 510, length 0

seq 1:95는 1번부터 94번 바이트까지(끝 번호 95는 다음 seq)라는 뜻이고, 서버는 ack 95로 그대로 응답했습니다. win 502는 헤더 필드 원값이며, 실제 윈도우는 502 × 2^7 = 64,256바이트입니다.

  • Handshake SYN에서 MSS·SACK_PERM·WS 옵션을 확인했다
  • 한 방향의 다음 seq = seq + len 규칙을 3개 이상 패킷에서 검산했다
  • 상대 ACK가 내 seq가 아니라 상대 seq에 대응한다는 것을 확인했다
  • win 원값과 scale을 곱해 실제 윈도우를 계산했다
  • (선택) netem 적용 시 Dup ACK → 재전송 → ACK 전진 흐름을 찾았다
  • 실습 후 방화벽 임시 규칙과 tc 설정을 제거했다

6. 패킷 / 로그 분석

6-1. Wireshark 분석 필드

필터의미
tcp.analysis.flagsWireshark가 표시한 모든 TCP 이상 표시
tcp.analysis.retransmission재전송으로 판단된 세그먼트
tcp.analysis.fast_retransmission중복 ACK 이후 빠른 재전송
tcp.analysis.duplicate_ack중복 ACK
tcp.analysis.lost_segment캡처상 앞 구간이 비어 있음 (캡처 누락일 수도 있음)
tcp.analysis.out_of_order순서가 뒤바뀐 도착
tcp.analysis.zero_window윈도우 0 광고
tcp.analysis.window_full송신 측이 상대 윈도우를 꽉 채움

Wireshark의 tcp.window_size_scalefactor 가 -1(unknown)이면 Handshake가 캡처에 없어 스케일을 모르는 상태, -2면 스케일을 사용하지 않는 연결입니다. 이 상태에서 계산된 윈도우 값은 믿으면 안 됩니다.

6-2. 정상일 수 있는 경우 vs 의심해야 하는 경우

관찰정상일 수 있는 경우의심해야 하는 경우
재전송 소량무선망·장거리 구간, 순간 혼잡특정 목적지·특정 시간대에만 반복 → 장비 드롭(IPS 인라인 차단 포함) 가능성
Zero Window수신 애플리케이션 일시 지연클라이언트가 의도적으로 작은/0 윈도우를 유지하며 연결만 오래 붙잡음(느린 읽기형 자원 고갈 원리)
lost_segment캡처 장비가 패킷을 놓침(미러링 과부하)실제 손실과 구분하려면 호스트 nstat 재전송 카운터와 비교
세션 중간 RST서버 타임아웃, 애플리케이션 종료seq가 현재 흐름과 동떨어진 RST, TTL이 앞뒤 패킷과 다른 RST → 경로상 장비나 제3자가 끼워 넣은 패킷일 가능성
ACK가 보낸 적 없는 데이터를 확인캡처 시작 전 데이터(중간부터 캡처)캡처가 완전한데도 발생 → 세션 위조·비대칭 경로 확인

6-3. 확인 명령

# 재전송·이상 표시가 있는 패킷만 요약 (tshark는 Wireshark CLI, 패키지: Rocky wireshark-cli / Ubuntu tshark)
tshark -r seq_lab.pcap -Y 'tcp.analysis.flags' -T fields \
  -e frame.number -e ip.src -e ip.dst -e tcp.seq -e tcp.ack -e tcp.len -e _ws.col.Info

# 세션 중간 RST의 seq와 TTL 확인
tshark -r seq_lab.pcap -Y 'tcp.flags.reset == 1' -T fields -e frame.number -e ip.src -e ip.ttl -e tcp.seq

📷 [실습 화면 삽입] Wireshark 패킷 상세에서 [SEQ/ACK analysis] 항목을 펼친 화면 — Next Sequence Number와 iRTT 표시


7. 보안관제 관점

흔적이 남는 곳

  • 패킷 캡처(pcap): seq/ack/window의 실제 값은 패킷에만 있습니다. 세션 로그만으로는 재전송·Zero Window를 판단할 수 없습니다.
  • 방화벽·IPS 세션 로그: 대부분 송수신 바이트·패킷 수·종료 사유 정도만 남습니다. "바이트 수가 비정상적으로 작고 종료 사유가 타임아웃" 같은 조합이 seq 흐름 분석이 필요하다는 신호가 됩니다.
  • 호스트: ss -ti, nstat의 재전송 카운터는 해당 서버에서 실제로 무슨 일이 있었는지 알려 주는 보조 증거입니다.

관제에서의 활용

  • 데이터 유출량 추정: 수신 측이 보낸 마지막 ACK 번호 − 첫 ACK 번호로, 그 반대 방향(송신 측 → 수신 측)으로 전달된 바이트 수를 추정할 수 있습니다(SYN·FIN이 소비한 1씩은 제외). 장비 로그의 바이트 수와 비교하면 교차 검증이 됩니다.
  • 차단 효과 확인: IPS가 세션을 끊었다면 RST가 어느 방향으로, 어떤 seq로 나갔는지 보면 실제로 차단이 적용됐는지 판단할 수 있습니다(IPS 동작은 06. 방화벽 · IDS/IPS 분석 시리즈에서 다룸).
  • 탐지 우회 원리 이해: IDS는 스트림을 재조립해 시그니처를 비교합니다. 순서 뒤바뀜·중복 재전송된 세그먼트를 IDS와 서버가 다르게 해석하면 탐지가 빗나갈 수 있어, IDS 재조립 설정이 중요합니다(06. 방화벽 · IDS/IPS 분석 시리즈에서 다룸).

한계와 오탐 주의

  • 캡처 지점이 한 곳이면 손실이 캡처 전인지 후인지 알 수 없습니다. lost_segment를 곧바로 "네트워크 손실"로 보고하면 안 됩니다.
  • 비대칭 라우팅 환경에서는 한 방향 패킷만 보여 ACK가 "보지 못한 데이터를 확인"하는 것처럼 보일 수 있습니다.
  • Wireshark의 분석 표시는 휴리스틱입니다. 판단 근거로 쓸 때는 실제 seq/ack 숫자를 함께 적어 두는 것이 좋습니다.

8. 핵심 정리

  • 시퀀스 번호는 "보내는 데이터의 첫 바이트 번호", ACK는 "다음에 받을 바이트 번호"이며, SYN·FIN은 번호를 1 소비합니다.
  • 누적 ACK가 전진하지 않고 반복되면(Dup ACK) 빈틈이 있다는 뜻이고, 이어서 같은 seq 재전송이 나옵니다.
  • 윈도우는 수신 측 여유 공간이며, Window Scale은 Handshake에서만 협상되므로 캡처에 SYN이 없으면 실제 값을 알 수 없습니다.
  • Zero Window는 네트워크가 아니라 수신 애플리케이션 쪽 문제이며, 의도적으로 유지되면 자원 고갈 공격의 원리가 됩니다.
  • 관제에서는 장비 로그의 바이트 수·종료 사유로 이상을 의심하고, pcap의 seq/ack/window 흐름과 호스트 재전송 카운터로 확인합니다.

다음 글: 12. MTU와 IP 단편화 — 큰 패킷이 쪼개질 때

profile
코드에 숨겨진 위협을 읽고 AI로 보안의 미래를 설계합니다. 프론트엔드 개발 경험을 자산 삼아 더 견고하고 지능적인 보안 운영 시스템을 구축해 나가는 과정을 기록합니다

0개의 댓글