요약

  • UDP ASSOCIATE는 방식 협상을 마친 TCP 연결을 통해 릴레이 상태를 만들고, 제어 연결이 끝나면 그 상태를 종료했다. 이는 흐름에 컨텍스트 수명을 부여했으며, UDP 데이터를 전달하거나 보장하지 않았다.
  • 반환된 릴레이 주소, 클라이언트 소스 주소, 각 패킷의 목적지, 보호 방식, 조립 상태는 서로 독립적인 진술이었다. 어느 하나도 사용자, 최종 수신자 또는 영향에 대한 충분한 증거가 아니었다.

진행 중인 트래픽은 갱신 허가가 아니었다

클라이언트가 릴레이의 UDP 포트로 음성이나 쿼리를 계속 보내다가, 모바일 네트워크 전환, NAT 상태 소실, 프록시 장애 등으로 TCP 연결이 사라질 수 있다. 새 패킷이 예전 포트에 도착하고 올바른 목적지를 담는다. 그것을 통과시키는 것이 자연스러운 연속처럼 보이기 쉽다.

그러나 1996년 3월에 발표된RFC 1928은 명확한 경계를 선택했다:UDP ASSOCIATE가 전달된 TCP 연결이 끝나면 UDP 어소시에이션도 끝난다. 릴레이 포트를 알고 패킷을 보낼 수 있다는 사실은 활동을 나타내지만, 방식과 정책을 검증했던 컨텍스트의 지속을 나타내지는 않는다.

페이로드는 TCP 내부를 통과하지 않았다. UDP는 사라지거나 중복되거나 순서가 어긋날 수 있는 별개의 패킷으로 남았다. TCP는 인증 방식의 선택과 어소시에이션 생성 요청을 실었고, 그 존재는 서버가 관찰할 수 있는 국소적 사실을 제공했다.

이렇게 프로토콜은 흐름 신뢰성을 부여하지 않고 새로운 종료 핸드셰이크를 발명하지 않으면서, 시작과 끝이 명확한 비연결형 서비스를 제공했다.

영(0)은 정책 부재가 아니라 정보 부족이었다

릴레이 요청 전에 클라이언트는 TCP를 통해 자신의 방식들을 제시했고, 서버가 하나를 선택했으며, 양측은 그에 해당하는 하위 협상을 마쳤다. 그 후에야CONNECT나BIND나UDP ASSOCIATE를 보낼 수 있었다.

UDP 요청은 클라이언트가 패킷을 보내는 데 사용할 것으로 예상되는 주소와 포트를 담았고, 서버는 어소시에이션을 이 값으로 제한할 수 있었다. 클라이언트가 아직 값을 모르면 주소와 포트를 0으로 설정해야 했다.

0은 ‘어떤 소스든 허용’을 의미하지 않았다. 요청 자체가 특정 값을 갖지 않는다는 선언이었다. 서버는 연결, 관찰, 로컬 정책으로부터 실제 소스를 추론해야 했다.

요청 성공은BND.ADDR과BND.PORT를 돌려주었다: 클라이언트가 SOCKS 래퍼를 보내야 하는 UDP 지점이다. 멀티홈 서버는 TCP 연결 주소와 다른 주소를 선택할 수 있으므로, 이 값은 릴레이의 입구였지 원격 호스트의 신원이 아니었다.

따라서 서로 다른 세 위치가 있었다: 제어 서버의 주소, 서버가 할당한 UDP 입구 주소, 그리고 각 패킷에 담긴 목적지. 이들을 ‘프록시 주소’ 한 칸에 합치면 실패 원인이 지워진다.

각 패킷은 자신의 목적지를 보존했다

UDP 래퍼는 reserved 필드,FRAG필드, 주소 유형, 목적지 주소와 포트, 그리고 데이터를 담았다. 어소시에이션은 클라이언트를 하나의 대상에 묶지 않았다; 오히려 패킷마다 새로운 릴레이 결정을 제출할 수 있게 했다.

릴레이는 래퍼를 읽고, 정책을 적용하고, 전송 전에 SOCKS 헤더를 제거했다. 원격 호스트에서 응답이 오면, 클라이언트가 릴레이가 본 소스를 알 수 있도록 같은 언어로 다시 감쌌다.

이 정보는 매칭에 유용하지만 호스트의 서명은 아니다. 릴레이는 래퍼를 수락하거나 거부했으며 전송을 시도했다는 것을 증명할 수 있다. 그러나 경로 도달, 애플리케이션 처리, 사용자 목적의 실현을 혼자서는 증명할 수 없다.

또한BND.ADDR은 영구적인 권한이 되지 않는다. TCP가 죽은 후 값이 클라이언트 메모리에 남아 있을 수 있지만, 예전 주소로 올바른 래퍼를 보낸다고 어소시에이션이 재생성되지는 않는다. 정보의 잔존은 허가의 잔존이 아니다.

소스 주소 제약은 신원이 아니었다

RFC 1928은 UDP 릴레이에게 SOCKS 서버로부터 클라이언트의 예상 IP 주소를 받아, 다른 주소에서 오는 패킷은 조용히 폐기하도록 요구했다. 이는 다른 호스트가 남이 만든 컨텍스트에 패킷을 쉽게 주입하는 것을 막는다.

그러나 IP는 NAT 뒤 여러 장치 사이, 한 장치 내 여러 프로세스 사이에 공유될 수 있고, 이동 시 바뀔 수 있다. 검사는 릴레이 경계에서의 네트워크 속성이 저장된 값과 일치했음을 증명할 뿐이다; 사람, 프로세스, 목적지의 동의를 증명하지 않는다.

인증은 선택된 방식에서 왔다.RFC 1929는 사용자 이름/비밀번호 방식을 정의했지만, 협상 안에서 평문으로 자격 증명을 전송하며 도청 가능한 환경에서의 사용을 경고했다. 한편RFC 1961은 GSS-API를 정의했으며, 인증과 함께 메시지 무결성과 선택적 기밀성을 협상할 수 있게 했다.

따라서 ‘SOCKS5’라는 표현은 하나의 보안 속성을 갖지 않는다. 기록은 선택된 방식, 결과, 보호 수준, 그 적용 범위를 보존해야 한다. IP 검사가 약한 방식이 남긴 공백을 메울 권리는 없다.

FRAG 메모리는 선택적이고 소멸 가능했다

SOCKS 래퍼에서FRAG=0은 패킷이 독립적임을 의미한다. 1~127 값은 조각 순서를 나타내고, 최상위 비트는 끝을 나타낸다. 이는 IP 단편화가 아니라 릴레이 프로토콜 내부의 순서다.

재조립 지원은 선택 사항이었다. 미지원 구현은 0이 아닌 모든 FRAG를 드롭해야 한다. 지원 구현은 큐와 타이머를 만들고; 시간이 끝나면 조각을 지우며, 처리했던 최고 번호보다 낮은 값이 오면 시퀀스를 재설정한다. 타이머는 5초 미만이면 안 되며, 사양은 가능한 한 단편화를 피하라고 권고했다.

여기에는 숨은 신뢰성이 없었다. ACK도 재전송 약속도 없었다. 시퀀스 완성은 로컬 페이로드를 구성하고 전송 결정을 내릴 수 있게 할 뿐, 도착을 증명하지 않는다. 상태의 폐기 가능성은 그 한계의 일부였다.

이 때문에 부재의 원인을 분리해야 한다: 소스 불일치, 미지원 FRAG, 순서 역행, 타이머 만료, 정책 거부, 또는 출력 이후의 소실. ‘UDP 실패’라는 단일 카운터는 수정 주체를 특정하지 못한다.

표준은 추측 대신 종료를 선택했다

idle timeout은 상태를 유지할 수 있지만, 잠자는 애플리케이션과 죽은 클라이언트를 구별하지 못한다. 패킷 도착으로 활동을 유지할 수 있지만, 포트를 아는 사람에게 암묵적 갱신 권한을 준다.

TCP 연결은 사람이나 애플리케이션 성공의 증거는 아니지만, 서버가 만들고, 관찰하고, 종료할 수 있는 상태다. 존재하는 동안 인증 방식과 정책과 UDP 입구를 살아있는 대화에 결부할 수 있다. 소멸하면 새 요청이 권한을 다시 세우는 대신, 컨텍스트를 알 수 없는 패킷에게 상속하지 않는다.

이 선택에는 대가가 있다: 짧은 TCP 장애가 건전한 UDP 경로를 끊을 수 있고, 이동이 제어와 소스를 함께 바꾸게 만들 수 있으며, 추가 socket을 소모한다. 그러나 이 경계는 검증 가능하고 복구 가능하다. 반면 고아가 된 패킷 릴레이를 오래 두는 것은 순간적으로 더 쉽고 책임 소명은 더 어렵다.

IPv6/IPv4 게이트웨이는 투명 라우터가 되지 않았다

2001년의 Informational 문서인RFC 3089는 SOCKS 기반 IPv6/IPv4 게이트웨이를 기술했다. 양쪽에서 애플리케이션 계층에서 연결을 종료했고, 구식 애플리케이션이 IPv6 주소를 저장할 수 없을 때 듀얼스택 노드에 DNS 확인을 위임할 수 있었다.

게이트웨이는 UDP ASSOCIATE를 포함한 SOCKS 명령을 상속했지만 투명 IP 라우팅으로 변하지 않았다. 요구된 이름, 확인 결과, 제어 어소시에이션, 릴레이된 데이터는 서로 다른 사물로 남았다.

역사적 기여는 실행 가능한 작은 계약이었다: 표준은 요청 언어를 정하고, 릴레이는 국소적 결정을 보존하며, 릴레이의 관찰이 하위 계층에 대한 판정이 되지 않는다. 실제 운영은 광범위한 중앙 명칭이 아니라 볼 수 있는 경계로부터 권한을 얻는다.

출처