요약

  • TCP는 한 엔드포인트가 상대방이 모르는 사이에 상태를 폐기하거나 잃어버려 양쪽의 연결 이력이 서로 어긋난 상태를 하프-오픈(half-open) 연결이라고 부릅니다.
  • 상태를 유지한 쪽이 데이터를 보내면 상태를 잃은 상대는 RST로 응답하고, 여전히 연결이 수립되어 있다고 믿던 쪽은 그 연결을 중단합니다.
  • 재부팅된 쪽이 다시 열려고 시도하면 낡은 상태를 가진 쪽의 시퀀스 공간 ACK가 불일치를 드러내고, 새로 여는 쪽은 RST를 보낸 뒤 일반적인 핸드셰이크를 계속 진행합니다.
  • 리셋은 기존 연결을 복원하거나 손실된 애플리케이션 작업을 되찾아 주지 않습니다. 낡은 전송 계층 상태를 제거해 새로운 이력을 시작할 수 있게 해 줄 뿐입니다.

두 개의 기억이 함께 든 연결

TCP는 애플리케이션에 '연결'을 제시하지만, 그 연결은 실제로는 두 개의 별개 엔드포인트에서 서로 호환되는 상태로 구현됩니다. 각 측은 시퀀스 번호, 승인(ACK) 진행 상황, 윈도우, 상태 머신에서의 위치를 기억합니다. 이 추상화는 두 기억이 일치하는 동안에만 유지됩니다.

RFC 793은 이 실패 사례를 직접 명명했습니다. 한 TCP가 상대가 모르는 사이에 종료되거나 중단되었거나, 크래시가 메모리를 파괴해 양쪽이 동기화되지 않았을 때 수립된 연결은 하프-오픈 상태가 됩니다. RFC 9293도 같은 정의를 유지합니다. 한 엔드포인트는 여전히 ESTABLISHED 상태를 보고할 수 있지만, 상대에게는 그 연결에 대한 전송 제어 블록이 전혀 없을 수 있습니다.

이는 TCP의 질서 있는 하프-클로즈(half-close)가 아닙니다. 하프-클로즈는 FIN으로 한 방향을 끝내면서 반대 방향을 의도적으로 열어 둡니다. 하프-오픈 연결은 우연한 불일치입니다. 한쪽은 상대가 더 이상 인식할 수 없는 공유 이력을 그대로 간직하고 있는 것입니다.

생존한 쪽이 먼저 보낼 때

가장 단순한 발견은 실패하지 않은 엔드포인트에서 시작됩니다. 피어 B는 여전히 자신과 A의 연결이 동기화되어 있다고 믿고 데이터를 보냅니다. 그러나 A는 재부팅되었고 일치하는 연결이 없습니다.

A 입장에서 이 세그먼트는 어떤 살아 있는 이력도 이어가는 것이 아닙니다. CLOSED 상태인 A는 이 수용 불가능한 비-리셋 세그먼트에 RST로 응답합니다. B는 수용 가능한 리셋을 받고, 자신이 수립되었다고 믿었던 연결을 중단하며 실패를 상위로 보고합니다. 사양들은 어느 방향이든 트래픽이 시도되면 하프-오픈 연결이 자동으로 리셋 상태로 바뀐다고 기술합니다.

이 리셋에는 B에게 돌아가는 잃어버린 애플리케이션 상태가 담겨 있지 않습니다. A가 실패하기 전에 어떤 바이트까지 처리되었는지, 트랜잭션이 다른 곳에서 커밋되었는지를 말해 주지도 않습니다. 리셋은 상대가 B가 기억하는 연결에 참여할 수 없다는 사실만을 말합니다.

잊혀진 쪽이 다시 시작할 때

짝이 되는 예시가 더 많은 것을 보여 줍니다. 재부팅 후 A는 자신에게는 새 연결인 것을 열려고 시도합니다. A는 새로운 초기 시퀀스 번호를 선택해 SYN을 보냅니다. 그러나 B에는 여전히 동기화된 상태의 기존 연결이 남아 있습니다.

도착한 SYN은 B가 그 기존 이력과 연관시키는 수신 윈도우 밖에 있습니다. B는 자신이 다음에 기대하는 시퀀스 번호를 밝히는 ACK로 응답합니다. A는 여전히 비동기화 상태입니다. 이 ACK는 A가 새 시도에서 보낸 것 중 어떤 것도 승인하지 않으므로, A는 호환되지 않는 이력을 인식하고 RST를 보냅니다.

B는 리셋을 처리해 낡은 연결을 중단합니다. A는 새 연결 수립을 계속 시도합니다. 낡은 상태가 제거되고 나면 문제는 평범한 3방향 핸드셰이크가 됩니다. 프로토콜은 두 이력을 병합하지 않습니다. 더 이상 공유할 수 없는 이력을 끝내는 것입니다.

불일치가 가시가 되는 리셋

이 시나리오들에서 리셋은 일반적인 적대의 표시도 아니고, 상태를 재구성하는 수리용 패킷도 아닙니다. 리셋은 서로 호환되지 않는 엔드포인트의 믿음을 비교한 결과의 최종 산출입니다. 시퀀스 번호와 승인이 불일치를 드러내고, RST는 한 엔드포인트가 상대가 검증할 수 없는 연결에 계속 의존하는 것을 막습니다.

이 역할은 세부 사항이 중요한 이유도 설명해 줍니다. 리셋 규칙은 어떤 연결에도 속하지 않는 세그먼트와 동기화된 연결에 속하는 세그먼트를 구분해야 하며, 현대의 리셋 수용 강화는 추가적인 보안 질문을 다룹니다. 이 질문들은 낡은 상태의 손실과 복구라는 주제를 다루는 역사적 하프-오픈 사례와 혼동해서는 안 됩니다. 스푸핑은 이 사례의 주제가 아닙니다.

애플리케이션 경계

TCP는 전송 계층의 모순을 해소할 수 있습니다. 그러나 애플리케이션이 어떻게 복구해야 하는지를 결정할 수는 없습니다. 재시작된 서비스는 로컬 상태를 재구축하거나, 작업을 재시도하거나, 체크포인트에서 이어가거나, 기존 세션을 거부할 수 있습니다. 클라이언트는 마지막 요청이 실패 전에 실제로 적용되었는지 알지 못할 수 있습니다.

따라서 RFC의 예시는 단속된 경계에서 멈춥니다. 리셋 이후 기존 전송 계층 연결은 사라지고, 새로운 핸드셰이크가 다른 연결을 수립할 수 있습니다. 인증, 트랜잭션 식별, 재생 안전성, 미전달 작업의 복구는 TCP 위의 프로토콜과 애플리케이션의 몫입니다.

출처