Дождитесь пустой строки после CONNECT
Прочитайте весь ответ HTTP/1 CONNECT, прежде чем передавать сокет в TLS, даже если строка состояния пришла первой.
Клиент видит 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 не сохраняет границы записи сервера.
Сделайте передачу явным состоянием парсера
- Соберите и разберите строку состояния. Используйте числовой код, а не точную английскую фразу причины.
- Продолжайте, пока не придёт полный терминатор раздела заголовка. Успешная строка состояния не делает оставшиеся поля невидимыми.
- При успешном ответе CONNECT считайте байт сразу после этого терминатора началом данных туннеля. При других ответах следуйте соответствующей обработке ответа HTTP и аутентификации.
- Передайте соединение и все неиспользованные байты через буферизованный интерфейс ввода-вывода, который поддерживает следующий протокол.
Последний шаг важен даже при хорошем поиске разделителя. Буферизованный читатель мог потребить больше сокета, чем запросил парсер. Запуск TLS на голом сокете с отказом от этого читателя может оставить байты в приватном буфере читателя.
Используйте срок на установку и ограничение размера заголовка. EOF до терминатора — это неполный ответ прокси, а не приглашение ждать вечно или всё же попробовать TLS.
Продумайте несколько границ
Представьте чтение, которое заканчивается после Proxy-Agent:. Парсеру всё ещё нужны байты заголовка HTTP. Представьте другое, которое заканчивается на середине последней последовательности CRLF. Это всё ещё не полный раздел заголовка. А теперь представьте полный успешный раздел заголовка, за которым в том же буфере идут байты туннеля: эти лишние байты должны пережить передачу.
407 — это другой исход. Он требует обработки аутентификации; он не разрешает TLS ClientHello в туннель, который никогда не открывался.
Предпочитайте поддерживаемый коннектор, если только реализация рукопожатия не является задачей. Эта граница относится именно к показанному здесь обмену HTTP/1. Прокси HTTP/2 несёт CONNECT в потоке с другой кадрировкой.