본문으로 건너뛰기

중복 ACK를 이용한 빠른 재전송과 SACK

뒤쪽 데이터가 먼저 도착하면 수신자는 같은 ACK를 반복해서 보냅니다. 송신자는 이 중복 ACK를 손실 신호로 삼아 RTO가 만료되기 전에 Fast Retransmit를 시작할 수 있습니다.

누적 ACK는 빈 구간을 넘지 않는다

TCP의 ACK 번호는 수신자가 다음에 받기를 기대하는 바이트의 시퀀스 번호입니다. ACK=1001이라면 1000번 바이트까지 연속해서 받았다는 뜻입니다.

이 상태에서 [1001, 2001) 구간이 손실되고 [2001, 3001) 구간이 먼저 도착했다고 가정해보겠습니다. 수신자는 2001번 이후의 데이터를 받았지만 1001번부터 시작하는 빈 구간이 남아 있으므로 ACK 번호를 3001로 올릴 수 없습니다. 대신 기존과 같은 ACK=1001을 보냅니다.

RFC 5681 3.2절은 순서가 어긋난 세그먼트가 도착하면 수신자가 중복 ACK를 바로 보내도록 권고합니다. 일반적인 지연 ACK처럼 잠시 기다리지 않고, 현재 어느 시퀀스 번호를 기대하는지 송신자에게 알립니다. 뒤의 데이터가 계속 도착하는 동안 빈 구간이 채워지지 않으면 같은 ACK 번호가 반복됩니다.

아래 예시는 미확인 데이터가 남아 있는 동안 ACK 번호와 광고 윈도우가 변하지 않은 순수 ACK가 세 번 도착하는 경우입니다. 각 데이터 세그먼트는 1000바이트라고 가정했습니다.

순서수신자에게 도착한 범위수신자의 응답송신자가 확인한 상태
1[1001, 2001) 손실응답 없음아직 손실 여부를 알 수 없음
2[2001, 3001)ACK=1001, SACK=[2001, 3001)첫 번째 중복 ACK
3[3001, 4001)ACK=1001, SACK=[2001, 4001)두 번째 중복 ACK
4[4001, 5001)ACK=1001, SACK=[2001, 5001)세 번째 중복 ACK, [1001, 2001) 재전송
5재전송한 [1001, 2001)ACK=5001빈 구간이 채워져 누적 ACK가 전진

SACK을 사용하지 않아도 위 조건을 만족하는 중복 ACK가 세 번 도착하면 Fast Retransmit가 시작됩니다. 다만 송신자가 알 수 있는 정보가 ACK=1001이라는 경계에 한정됩니다.

중복 ACK 세 개는 손실의 증명이 아니다

같은 ACK 번호가 반복됐다는 사실만으로 특정 세그먼트가 사라졌다고 단정할 수는 없습니다. RFC 5681은 중복 ACK가 생기는 원인으로 세그먼트 손실 외에도 세그먼트 순서가 바뀌거나 ACK·데이터가 복제되는 경우를 듭니다.

예를 들어 [1001, 2001)이 느린 경로를 지나고 뒤의 세그먼트 세 개가 먼저 도착하면 수신자는 같은 ACK를 세 번 보냅니다. 송신자는 이를 손실로 판단해 [1001, 2001)을 다시 보낼 수 있지만, 원본 세그먼트가 조금 늦게 도착하면 결과적으로 불필요한 재전송이 됩니다.

중복 ACK 세 개는 손실을 증명하는 조건이 아니라 RTO보다 일찍 복구를 시작하기 위한 기준입니다. 패킷 캡처에서는 시퀀스 번호와 도착 시간을 함께 봐야 합니다.

Fast Retransmit가 동작하지 않는 경우

일반적인 손실 상황에서 Fast Retransmit를 시작하려면 손실된 구간 뒤의 데이터가 수신자에게 도착해야 합니다. 뒤의 세그먼트가 도착할 때마다 중복 ACK를 만들 수 있기 때문입니다.

전송 중인 데이터가 적어서 손실 지점 뒤에 세그먼트가 하나나 둘뿐이면 중복 ACK 세 개를 만들지 못할 수 있습니다. RFC 5681의 Limited Transmit는 첫 번째와 두 번째 중복 ACK에서 조건이 맞으면 새 데이터를 보내 ACK를 더 받을 기회를 만듭니다. 이때 보낼 데이터가 남아 있고 수신 윈도우가 허용해야 하며, 전송 중인 데이터는 cwnd + 2 × SMSS를 넘지 않아야 합니다. SACK을 사용한다면 해당 중복 ACK에 새 SACK 정보도 있어야 합니다.

마지막 세그먼트가 손실된 경우에는 상황이 더 분명합니다. 뒤이어 도착할 데이터가 없으므로 수신자는 순서가 어긋났다는 중복 ACK를 만들 수 없습니다. 결국 기본적인 복구 경로는 RTO가 됩니다.

Fast Retransmit와 Fast Recovery는 역할이 다르다

Fast Retransmit는 중복 ACK를 손실 신호로 보고 누락된 세그먼트를 다시 보내는 동작입니다. Fast Recovery는 그 뒤 혼잡 윈도우를 조정하며 전송을 이어가는 절차입니다.

RFC 5681의 기본 절차에서는 세 번째 중복 ACK에서 ssthresh를 줄이고 cwnd를 일시적으로 늘립니다. 재전송을 시작할 때 미확인이던 데이터가 모두 확인되면 Fast Recovery를 끝내고 cwndssthresh로 낮춥니다.

SACK은 받은 범위를 알려준다

누적 ACK만으로도 첫 번째 빈 구간의 시작은 알 수 있습니다. 하지만 한 번에 여러 세그먼트가 손실되면 그 뒤의 상태는 분명하지 않습니다. ACK 번호가 첫 번째 빈 구간에 머물러 있기 때문입니다.

SACK(Selective Acknowledgment)은 수신자가 순서와 상관없이 이미 받은 바이트 범위를 함께 알려주는 TCP 옵션입니다. SACK 옵션을 처리할 수 있는 TCP는 SYN에 SACK-Permitted를 넣어 이 사실을 알립니다. 이 옵션을 받은 쪽은 이후 해당 상대에게 보내는 ACK에 하나 이상의 SACK 블록을 담을 수 있습니다.

RFC 2018의 SACK 블록은 [Left Edge, Right Edge) 형식입니다. 왼쪽 값은 받은 범위의 첫 시퀀스 번호이고, 오른쪽 값은 마지막으로 받은 바이트의 다음 시퀀스 번호입니다. 무엇이 손실됐는지가 아니라 무엇을 받았는지를 나타냅니다. SACK을 사용해도 TCP 헤더의 ACK 번호가 가진 누적 ACK의 의미는 바뀌지 않습니다.

다음 상태를 예로 들 수 있습니다.

text
누적 ACK : 1001
전송한 범위 : [1001, 6001)
SACK 블록 : [2001, 3001), [4001, 6001)
송신자가 보는 빈 구간: [1001, 2001), [3001, 4001)

수신자가 보낸 것은 현재 받았다고 알린 두 범위입니다. 송신자는 자신이 전송한 범위, 누적 ACK, SACK 블록을 비교해 그 사이의 빈 구간을 손실 후보로 추정합니다. SACK 옵션의 크기는 TCP 옵션 공간에 제한되므로 모든 수신 범위가 매 ACK에 담긴다는 보장도 없습니다.

SACK을 바탕으로 재전송 구간을 고른다

SACK은 받은 범위를 알릴 뿐 재전송 대상을 정하지는 않습니다. 송신자는 누적 ACK와 SACK 정보를 scoreboard에 기록하고, 아직 네트워크에 남아 있다고 추정되는 데이터 양을 계산해 보낼 구간을 고릅니다. SACK 블록 사이에 빈 구간이 보인다고 즉시 손실로 확정하는 것은 아닙니다. 이 송신자 측 복구 절차는 RFC 6675에 정의되어 있습니다.

수신자는 한 번 SACK으로 알린 데이터를 나중에 버릴 수도 있습니다. 따라서 해당 범위가 누적 ACK에 포함되기 전까지는 송신 버퍼에서 제거하면 안 됩니다.