요약

  • TCP는 호출이나 응답이 아니라 순서가 보존된 바이트 스트림을 유지했습니다. 그래서 ONC RPC는 각 RPC 메시지를 명시적으로 개수가 표시된 하나 이상의 프래그먼트로 구성된 하나의 기록 안에 담았습니다.
  • 모든 프래그먼트는 4바이트 빅 엔디안 마커로 시작했습니다. 하위 31비트는 그 프래그먼트의 데이터 길이를, 최상위 비트는 단지 그 바이트들 이후에 기록이 끝난다는 사실만을 나타냈습니다.
  • 완전한 기록은 프레이밍을 제공했지 진실을 제공하지 않았습니다. XDR 디코딩, XID 상관, 인증, 절차 수용, 내구성 있는 실행은 여전히 별개의 판단이었습니다.

2바이트는 아직 마커가 아니었다

서버가 소켓에서 4바이트를 요청했는데 2바이트만 받았다고 가정해 봅시다. 아무것도 비정상이 아닙니다. TCP는 순서 있는 스트림의 첫 부분을 전달한 것이고, 나머지 2바이트는 다음 읽기에서 도착할 수 있습니다. 파서는 페이로드 바이트를 빌려 쓰거나 길이를 추측하거나 읽기 경계를 구두점으로 취급할 수 없습니다. 2바이트를 보관하고 기다려야 합니다.

그 반대도 다음 시스템 호출에서 일어날 수 있습니다. 한 번의 읽기가 그 마커의 나머지, 프래그먼트 전체, 두 번째 프래그먼트의 마커와 본체, 그리고 이어지는 RPC 메시지의 시작까지 한꺼번에 돌려줄 수 있습니다. 신뢰성은 송신자의 write 호출이나 수신자의 read 호출을 프로토콜 객체로 보존하지 않습니다.

RFC 793은 이미 TCP의 순서화된 옥텟 서비스를 기술하며 PUSH가 기록 경계 표식을 제공하지 않는다고 경고했습니다. 현재의RFC 9293도 여전히 TCP를 세그먼트로 전달되는 신뢰성 있고 순서가 보존되는 바이트 스트림 서비스로 정의합니다. 세그먼트 경계는 전송 계층의 포장일 뿐입니다. RPC는 그 위에 자신만의 문법이 필요했습니다.

4바이트가 담은 두 가지 권한

1988년 4월에 발표된 공개 ONC RPC 사양RFC 1050은 이 메커니즘을 레코드 마킹(record marking)이라 불렀습니다. 하나의 RPC 메시지가 하나의 RM 기록에 들어갔고, 기록은 하나 이상의 프래그먼트를 담았습니다.RFC 1057은 두 달 뒤 같은 와이어 형식을 유지한 채 그 문서를 대체했습니다.

각 프래그먼트는 상위 바이트부터 하위 바이트 순서의 4바이트 부호 없는 워드로 시작합니다. 하위 31비트는 이 프래그먼트 뒤에 이어지는 데이터 바이트 수를 나타내며, 0부터2^31 - 1까지 어떤 값이든 가능합니다. 최상위 비트는 부울입니다. 0이면 같은 기록에 또 다른 프래그먼트가 속한다는 뜻이고, 1이면 이것이 마지막 프래그먼트라서 선언된 본체가 소비된 이후에만 기록이 끝난다는 뜻입니다.

이 필드들이 부여하는 권한은 좁습니다. 길이 필드는 수신자가 정확히 한 프래그먼트를 넘어갈 수 있게 하고, 종단 비트는 그 이후의 위치에서 감싸는 기록을 닫을 수 있게 합니다. 헤더에서 본 최상위 비트는 본체 이전에 기록을 끝내지 않고, 비트가 0일 때 선언된 길이의 본체가 도착했다고 기록이 끝나지도 않습니다.

수신자는 임의의 읽기에 걸쳐 기록을 재구성했다

따라서 견고한 파서는 작은 상태 기계를 갖습니다. 먼저 마커 4바이트를 축적합니다. 최상위 비트를 길이에서 분리합니다. 그다음 많은 읽기에 나뉘어 도착하더라도 정확히 그만큼의 스트림 바이트를 소비합니다. 데이터를 현재 기록에 더하고 저장해 둔 비트를 검사합니다.

비트가 0이었다면 다음 4바이트는 같은 기록의 또 다른 프래그먼트 마커입니다. 비트가 1이었다면 축적된 프래그먼트들이 하나의 완전한 RPC 메시지를 이루고, 다음 4바이트는 새 기록을 시작합니다. 파서는 TCP 세그먼트 경계가 필요 없습니다. 순서 있는 바이트, 정확한 개수, 보관된 상태만 있으면 됩니다.

길이 0의 프래그먼트는 명시된 범위 안에서 합법적입니다. 마커를 가지고 종단 판단도 담고 있습니다. 반대로 선언된 본체 도중에 스트림이 닫히면 짧지만 완전한 기록이 전달된 것이 아니라 불완전한 프래그먼트가 전달된 것입니다. 레코드 마킹은 빠진 경계를 식별할 수 있지만, 연결이 사라지기 전에 원격 절차가 실행되었는지는 말할 수 없습니다.

프래그먼트는 패킷이 아니라 프레이밍 단어였다

어휘는 쉽게 뒤섞입니다. RM 프래그먼트는 IP 프래그먼트도, TCP 세그먼트도, 소켓 write도, 하나의 XDR 필드도 아닙니다. 하나의 RM 프래그먼트가 많은 TCP 세그먼트에 걸칠 수도 있습니다. 여러 프래그먼트 마커와 여러 완전한 기록이 하나의 세그먼트나 수신 버퍼를 공유할 수도 있습니다. 재전송은 재구성된 스트림을 그대로 둔 채 패킷 흔적을 바꿀 수 있습니다.

마지막 프래그먼트 비트도 EOF나 성공 플래그가 아닙니다. CALL은 하나의 RPC 메시지이고 REPLY는 또 다른 메시지입니다. TCP 위에서 전달될 때 각자 자기 RM 기록을 받습니다. 종단 비트는 그 인코딩된 메시지가 어디서 멈추는지를 말할 뿐입니다. 프로그램 번호가 존재하는지, 자격 증명이 통과했는지, 인수가 디코딩되었는지, 절차가 커밋되었는지, 응답이 내구성 저장소에 도달했는지는 말하지 않습니다.

PSH를 구두점으로 읽고 싶은 오랜 유혹에 대한 실용적인 답이 여기 있습니다. PUSH는 가용 바이트가 이동하는 시점에 영향을 줄 수 있습니다. 레코드 마킹은 다음 프레이밍 단위에 몇 바이트가 속하는지, 그 단위가 언제 완성되는지를 RPC 파서에 알려 줍니다. 타이밍과 문법은 서로 다른 계층에 있습니다.

마커는 의도적으로 XDR 바깥에 놓였다

RPC 메시지 자체는 XDR(External Data Representation)로 기술됩니다.RFC 4506은 XDR의 표준 값, 길이, 패딩, 타입 구조를 정의합니다. 디코더가 CALL이나 REPLY를 이해하려면 이 공유 표현이 필요합니다.

그러나 레코드 마커 사양들은 자신들의 4바이트 헤더가 XDR 표준 형식이 아니라고 명시적으로 말합니다. 바이트 순서는 XDR 부호 없는 정수를 닮았지만, 그 역할은 안의 RPC 메시지를 디코딩하기 이전에 수행됩니다. 프레이밍이 이미 전체 값을 디코딩한 상태에 의존한다면, 스트림 파서는 스스로 찾으려던 답을 미리 알아야 할 것입니다.

이 분리는 두 가지 유용한 실패를 만듭니다. 수신자는 절차를 해석하기 전에 불가능하거나 과도한 프레이밍 주장을 거부할 수 있습니다. 또한 완벽하게 프레이밍된 기록을 받으면서 그 XDR이나 RPC 내용이 무효일 수도 있습니다. “메시지가 여기서 끝났다”와 “메시지가 올바른 형식이었다”는 여전히 서로 다른 증거입니다.

31비트가 2기가바이트 할당을 명령하지 않았다

길이 필드는 한 프래그먼트의 상한이지 권장 버퍼 크기도 전체 기록의 최대치도 아닙니다. 하나의 기록이 여러 프래그먼트를 담을 수 있기 때문에 기본 규칙은 누적 메시지 한도를 공표하지 않습니다. 로컬 정책을 적용하기 전에 광고된 모든 길이를 할당하는 문자 그대로의 구현은 와이어 좌표를 메모리에 대한 원격 권한으로 바꿔 버립니다.

따라서 수신자의 예산은 표준을 둘러싼 운영적 추론입니다: 누적 기록 바이트, 프래그먼트 수, 버퍼링된 바이트, 시간에 상한을 두고, RPC 프로그램의 필요에 따라 스트리밍하거나 거부하며, 신뢰할 수 없는 길이가 서비스를 고갈시키기 전에 멈춥니다. 선택한 한도는 로컬입니다. 마커는 여전히 그 한도가 작동하는 대상이 되는 감사 가능한 사실을 제공합니다.

같은 주의는 오류 복구에도 적용됩니다. RFC들은 구획화가 프로토콜 오류를 감지하고 어쩌면 복구하는 데 도움이 될 수 있다고 말하지만, 다음 그럴듯한 4바이트 워드를 찾는 스캔을 표준화하지는 않습니다. 페이로드에는 어떤 패턴이든 담길 수 있습니다. 프레이밍 위반 이후에는 우연히 겹치는 바이트들을 새 기록으로 취급하기보다 연결을 닫는 쪽이 더 진실에 가까울 수 있습니다.

와이어 규칙은 살아남았다가, 그것을 대체한 전송 계층을 만났다

RFC 1831은 1995년 이 형식을 표준 트랙으로 옮겼습니다.RFC 5531은 2009년 와이어상 프로토콜 변경 없이 그것을 대체했습니다. 같은 최상위 비트와 31비트 개수가 살아남은 이유는 TCP가 결여한 것을, 그리고 그것만을 표현했기 때문입니다.

그들의 권한은 전송 계층에 따른 것이었습니다.RFC 8166은 RPC-over-RDMA 프레이밍을 전송 스트림과 페이로드 스트림으로 정의합니다. RDMA 자체가 다른 RPC 프레이밍 규칙을 갖는 전송 위에 놓이는 경우를 포함하여, TCP 레코드 마킹을 비롯한 모든 다른 RPC 프레이밍을 명시적으로 대체합니다. 동적 전환은 서로 다른 RPC 메시지 사이에서, 전송 계층과 조화를 이루며 일어나야 합니다.

그 나중의 경계가 원래 설계를 분명히 합니다. 레코드 마킹은 RPC 절차의 영원한 속성이 아니었습니다. 하나의 메시지 모델과 하나의 바이트 스트림 전송 사이의 어댑터였습니다.

출처와 증거 한계

닫힌 기록은 RFC 793, RFC 1050, RFC 1057, RFC 1831, RFC 4506, RFC 5531, RFC 8166, RFC 9293으로 이루어져 있습니다. 이 문서들은 형식, 계보, 계층 경계를 확립합니다. 현재의 사용량을 측정하거나, 라이브러리를 인증하거나, 포착된 연결을 기술하거나, 어떤 절차의 신원, 인가, 정확성, 결과를 증명하지는 않습니다.

역사적 성취는 정확했지만 소박했습니다. 4바이트는 모든 패킷과 API 호출이 스트림을 서로 다르게 나누더라도 두 피어가 RPC 메시지의 끝을 어디에서 합의할 수 있게 했습니다. 이 메커니즘이 계속 신뢰할 수 있었던 이유는 마지막 비트가 결코 안의 작업이 끝났다고 주장하지 않았기 때문입니다.