Подтвердить что ты не робот

Как правильно отправлять двоичные данные по протоколу HTTPS?

Я отправляю двоичные данные с клиента (Debian 6.0.3) на сервер (Windows Server 2003). Чтобы обойти большинство брандмауэров, я использую HTTPS POST. Клиент и сервер реализуются с использованием Boost.Asio и OpenSSL. Сначала я реализовал простейшую возможную версию, и она отлично работала.

Заголовок HTTP:

POST / HTTP/1.1
User-Agent: my custom client v.1

[binary data]

([binary data] не кодируется base64, если это имеет значение)

Затем на другой клиентской машине это не удалось (подключено к тому же серверному компьютеру). Поведение нестабильно. Соединение всегда устанавливается нормально (порт 443). В большинстве случаев я могу передать SSL-подтверждение, но сервер не получает данных (почти никаких данных, иногда пакет или два фактически получены). Иногда я получаю сообщение об ошибке подтверждения связи "короткое чтение". Иногда я получаю недействительные данные.

Клиент подключается к серверу, рукопожатиям, отправляет HTTP-заголовок POST, а затем бесконечно отправляет двоичные данные, пока что-то не так. Для тестирования я использую специально созданный сертификат SSL.

Код сервера:

namespace ssl = boost::asio::ssl;
ssl::context context(io_service, ssl::context::sslv23);
context.set_options(ssl::context::default_workarounds | ssl::context::no_sslv2);
context.use_certificate_chain_file("server.pem");
context.use_private_key_file("server.pem", boost::asio::ssl::context::pem);

ssl::stream<tcp::socket> socket(io_service, context);

// standard connection accepting

socket.async_handshake(ssl::stream_base::server, ...);
...
boost::asio::async_read_until(socket, POST_header, "\r\n\r\n", ...);
...

Клиентский код:

ssl::context context(io_service, ssl::context::sslv23);
context.load_verify_file("server.crt");
socket.reset(new ssl::stream<tcp::socket>(io_service, context));
socket->set_verify_mode(ssl::verify_none);

// standard connection

socket.async_handshake(ssl::stream_base::client, ...);
...

(обработка ошибок опущена вместе с не соответствующим кодом)

Как вы можете видеть, это самое простое соединение SSL. Что не так? Может ли причина быть брандмауэром?

Я пробовал простой TCP без SSL по тому же 443-порту, это прекрасно работает.

EDIT:

Пробовал добавлять "Content-Type: application/octet-stream", не помогает.

ИЗМЕНИТЬ 2:

Обычно я получаю заголовок HTTP POST. Затем я отправляю куски данных как chunk-size(4 bytes)chunk(chunk-size bytes).... Сервер получает chunk-size штраф, но затем ничего. Клиент не уведомляет о проблемах сервера (без ошибок) и продолжает отправлять данные. Иногда сервер может получать кусок или два, иногда он получает недействительный chunk-size, но в большинстве случаев просто ничего.

ИЗМЕНИТЬ 3:

Сравнивая захваченный трафик на клиенте и сервере, не обнаружил различий.

Решение

Я был введен в заблуждение с самого начала этой проблемой. Сузили его до неожиданных деталей:

Отправка через сокет SSL завершается с ошибкой, если я использую мультибуферы Boost.Asio в Boost v.1.48 (самый последний в данный момент). Пример:

// data to send, protocol is [packet size: 4 bytes][packet: packet_size bytes]
std::vector<char> packet = ...;
uint32_t packet_size = packet.size();
// prepare buffers
boost::array<boost::asio::const_buffer, 2> bufs = {{boost::asio::buffer(&packet_size, sizeof(packet_size)), boost::asio::buffer(packet)}};
// send multi buffers by single call
boost::asio::async_write(socket, bufs, ...);

Отправка отдельно packet_size и packet в этом примере работает вокруг проблемы. Я далек от любого подозрительного поведения в качестве ошибки, особенно если это связано с библиотеками Boost. Но это действительно похоже на ошибку. Пробовал Boost v.1.47 - отлично работает. Пробовал с обычным сокетом TCP (а не с SSL одним) - отлично работает. То же самое и для Linux, и для Windows.

Я собираюсь найти какие-либо сообщения об этой проблеме в списке рассылки Asio и сообщит об этом, если ничего не найдено.

4b9b3361

Ответ 1

Я был введен в заблуждение с самого начала этой проблемой. Сузили его до неожиданных деталей:

Отправка через сокет SSL завершается с ошибкой, если я использую мультибуферы Boost.Asio в Boost v.1.48 (самый последний в данный момент). Пример:

// data to send, protocol is [packet size: 4 bytes][packet: packet_size bytes]
std::vector<char> packet = ...;
uint32_t packet_size = packet.size();
// prepare buffers
boost::array<boost::asio::const_buffer, 2> bufs = {{boost::asio::buffer(&packet_size, sizeof(packet_size)), boost::asio::buffer(packet)}};
// send multi buffers by single call
boost::asio::async_write(socket, bufs, ...);

Отправка отдельно packet_size и packet в этом примере работает вокруг проблемы. Я далек от любого подозрительного поведения в качестве ошибки, особенно если это связано с библиотеками Boost. Но это действительно похоже на ошибку. Пробовал Boost v.1.47 - отлично работает. Пробовал с обычным сокетом TCP (а не с SSL одним) - отлично работает. То же самое и для Linux, и для Windows.

Я собираюсь найти какие-либо сообщения об этой проблеме в списке рассылки Asio и сообщит об этом, если ничего не найдено.

Ответ 2

Если вам не нужно работать перед веб-сервером, у вас нет для использования протокола HTTPS. С точки зрения брандмауэра HTTPS выглядит еще другое соединение SSL, и он не знает, что происходит. Поэтому, если Единственное, что вам нужно - это просто передать данные, а не на фактический веб-сервер, использовать просто соединение SSL через порт 443.

Итак, просто устраните ваше SSL-соединение, проблема не имеет ничего общего с HTTP.


Если вы хотите использовать веб-сервер HTTP, а не пользовательский клиент:

Две точки:

  • Вам нужно указать Content-Length.
  • Если вы используете HTTP/1.1, вам нужно указать заголовок узла.

Простейшим может быть

POST /url HTTP/1.0
User-Agent: my custom client v.1
Content-Type: application/octet-stream
Content-Length: NNN

Actual Content

Или для HTTP/1.1

POST /url HTTP/1.1
Host: www.example.com
User-Agent: my custom client v.1
Content-Type: application/octet-stream
Content-Length: NNN

Actual Content

Примечание. вы не можете отправлять бесконечные данные. Протокол HTTP требует фиксированной длины содержимого и большую часть времени веб-серверы сначала загружали данные, прежде чем передавать их на бэкенд.

Таким образом, вам придется передавать данные кусками.

Ответ 3

(EDIT: Я изначально удалил это, потому что понял, что он действительно не использует HTTP. После комментария, где вы думаете, что у вас может быть прокси-сервер MITM, и должен использовать правильный HTTP, я удаляю/редактирую.)

POST / HTTP/1.1
User-Agent: my custom client v.1

[binary data]

Будь то бинарные данные или нет, в обычном HTTP или с SSL/TLS вам понадобится заголовок Content-Length или использовать chunked transfer encoding. Этот этот раздел спецификации HTTP. Также полезно использовать заголовок Content-Type.

Копирование с чередующимися передачами происходит, когда вы не обязательно знаете длину потока. (При отправке данных всегда требуется какая-либо форма разделителей, если только для надежного обнаружения, когда она заканчивается.)

Говоря об этом, вы должны выяснить, находитесь ли вы за прокси-сервером MITM, который смотрит на прикладной уровень поверх SSL/TLS, если вы получаете сертификат, а не сервер. Если вы все еще получаете успешное рукопожатие с вашим выигранным сертификатом сервера, такого прокси-сервера нет. Даже прокси-сервер HTTP использовал бы CONNECT и ретранслировал бы все, не изменяя соединение SSL/TLS (и, таким образом, без изменения исходного псевдо-HTTP сверху).