Клиент видит 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 в потоке с другой кадрировкой.

Похожие статьи