클라이언트는 200 Connection Established 를 보고 TLS를 시작한 뒤, TLS 레코드가 있어야 할 자리에서 곧바로 엉터리 값을 보고합니다. 목적지의 암호 설정을 탓하기 전에 끝나지 않은 HTTP 응답이 있는지 찾아보세요.

HTTP/1 CONNECT 교환에서 상태 줄은 응답의 시작일 뿐입니다. 그 뒤에 헤더 필드가 이어질 수 있습니다. CONNECT 응답이 성공했다면 헤더 구간을 끝내는 빈 줄이 터널로의 전환을 표시합니다.

HTTP/1.1 200 Connection Established\r\n
Proxy-Agent: example\r\n
\r\n

이 바이트들은 한 번의 읽기로, 여러 번의 읽기로, 또는 뒤따르는 바이트까지 함께 담은 읽기로 도착할 수 있습니다. TCP는 서버의 쓰기 경계를 보존하지 않습니다.

넘겨주기를 명시적인 파서 상태로 만드세요

  1. 상태 줄을 모아 파싱하세요. 정확한 영어 이유 문구 대신 숫자 상태를 사용하세요.
  2. 헤더 구간의 종결자가 완전히 도착할 때까지 계속 읽으세요. 상태 줄이 성공했다고 나머지 필드가 사라지지는 않습니다.
  3. 성공한 CONNECT 응답에서는 그 종결자 바로 다음 바이트를 터널 데이터의 시작으로 보세요. 다른 응답에서는 그에 맞는 HTTP 응답과 인증 처리를 따르세요.
  4. 연결과 소비되지 않은 바이트를 다음 프로토콜이 지원하는 버퍼 입출력 인터페이스로 넘기세요.

구분자 탐색이 좋아도 마지막 단계는 중요합니다. 버퍼 리더가 파서가 요청한 것보다 소켓을 더 많이 소비했을 수 있습니다. 그 리더를 버린 채 맨 소켓에서 TLS를 시작하면 바이트가 리더의 사설 버퍼에 남을 수 있습니다.

설정 기한과 헤더 크기 한도를 두세요. 종결자 전의 EOF는 불완전한 프록시 응답이며, 영원히 기다리거나 그래도 TLS를 시도하라는 초대가 아닙니다.

여러 경계를 따져 보세요

어떤 읽기가 Proxy-Agent: 뒤에서 끝난다고 해 봅시다. 파서에는 HTTP 헤더 바이트가 더 필요합니다. 또 다른 읽기가 마지막 CRLF 시퀀스 중간에서 끝난다고 해 봅시다. 그것도 아직 완전한 헤더 구간이 아닙니다. 이제 같은 버퍼에서 완전한 성공 헤더 구간 뒤에 터널 바이트가 따라온다고 해 봅시다. 그 여분의 바이트는 넘겨주기를 견뎌야 합니다.

407은 다른 결과입니다. 인증 처리가 필요하며, 한 번도 열리지 않은 터널로 TLS ClientHello를 보내도록 허용하지 않습니다.

핸드셰이크 구현 자체가 작업이 아니라면 유지 관리되는 커넥터를 고르세요. 이 경계는 여기서 보여 준 HTTP/1 교환에 해당합니다. HTTP/2 프록시는 프레이밍이 다른 스트림에서 CONNECT를 나릅니다.

관련 글