← 문제로

43. 신뢰성 UDP의 ACK 의미 불일치로 인한 유실 패킷의 조기 폐기

난이도 중
내 리뷰 · C#
해설 · C#

해설 — 신뢰성 UDP의 ACK 의미 불일치로 인한 유실 패킷의 조기 폐기

난이도: 중

요약

수신 측은 (A) 에서 "가장 높이 본 seq(_highestSeen)" 로 ACK 를 보내는데, 송신 측은 (B) 에서 그 값을 누적 ACK(cumulative ACK) 로 해석해 "ackSeq 이하 전부 확인됨" 으로 처리한다. 두 쪽의 ACK 의미가 어긋나, 갭 너머의 seq 를 ACK 하는 순간 아직 전달되지 않은 유실 패킷이 재전송 대상에서 제거되어 영영 전달되지 않는다.

문제점

  • [프로토콜 의미 불일치] 누적 vs 최고수신 혼동: 누적 ACK 는 "이 값까지 연속으로(빈칸 없이) 받았다" 를 뜻해야 한다. 그런데 (A) 는 연속 여부와 무관하게 _highestSeen 을 보낸다.
  • 재현(seq=5 유실, 6·7 도착):
    1. 6 도착 → _reorder={6}, _highestSeen=6, _expected=5 그대로 → SendAck(6).
    2. 7 도착 → _reorder={6,7}, _highestSeen=7 → SendAck(7).
    3. 송신 측 OnAck(7) → _unacked 에서 5,6,7 모두 k <= 7 이라 제거.
    4. 유실된 seq=5 가 재전송 버퍼에서 사라졌으므로 OnRetransmitTick 이 다시 보내지 않는다.
    5. 수신 측은 _expected=5 에서 영원히 막혀 6,7 도 상위로 전달하지 못한다. → 데이터 영구 유실 + 전달 정지(head-of-line 영구 정지).
  • 근본 원인: ACK 는 "마지막 연속 수신 지점(_expected-1)" 을 실어야 누적 제거와 정합한다. _highestSeen 은 갭을 건너뛴 낙관적 값이라 누적 의미와 충돌한다.

수정안

누적 ACK 로 통일한다. 수신 측이 "마지막 연속 수신 seq" 를 ACK 하고, 송신 측은 그 값까지만 제거한다. (선택적 확인이 필요하면 SACK 비트맵을 별도로 전달한다.)

// 수신 측: 연속 수신 지점을 ACK
public void OnData(uint seq, byte[] payload)
{
    uint cumAck() => _expected - 1;              // _expected==0 이면 "아직 없음" 을 표현할 값 규약 필요
    if (seq < _expected) { _link.SendAck(cumAck()); return; }
    _reorder[seq] = payload;
    while (_reorder.TryGetValue(_expected, out var p))
    { _app.Deliver(p); _reorder.Remove(_expected); _expected++; }
    _link.SendAck(cumAck());                      // 갭이 있으면 갭 이전까지만 ACK → 유실분은 계속 재전송됨
}

_expected 를 "다음에 기다리는 seq" 로 두면, 갭이 있는 한 누적 ACK 는 갭 이전에 머물고, 송신 측 OnAck 의 k <= ackSeq 제거는 유실 패킷(seq=5)을 건드리지 않으므로 타이머에 의해 재전송된다.

더 나은 설계

  • 누적 ACK + 선택적 ACK(SACK): 누적 지점 외에 "그 뒤로 개별 수신한 seq" 를 비트맵으로 함께 보내면, 송신 측이 유실분만 콕 집어 빠르게 재전송(fast retransmit)할 수 있어 갭 복구가 빨라진다.
  • seq 랩어라운드: uint 비교 k <= ackSeq 는 시퀀스가 한 바퀴 돌면 오작동한다. 시리얼 산술 (RFC1982식 부호 비교)로 판정해야 한다.
  • 버퍼 상한: 재정렬 버퍼·미확인 버퍼에 크기 상한을 둬 갭이 오래 지속될 때 무한 증가를 막는다.
  • 트레이드오프: 순수 누적 ACK 는 단순하지만 갭 복구가 느리고(타임아웃 대기), SACK 는 빠르지만 구현·대역폭 오버헤드가 있다.

면접 포인트

  • 누적 ACK 의 정확한 의미("연속 수신 지점")와, 이를 어기면 왜 데이터가 유실되는가.
  • 누적 ACK vs 선택적 ACK(SACK)의 복구 속도·복잡도 트레이드오프(TCP 의 SACK 옵션과 대응).
  • 시퀀스 번호 비교에서 랩어라운드를 시리얼 산술로 다뤄야 하는 이유.