19 сентября 2011 г.

Выключение компрессии в OpenSSL 0.9.8

В OpenSSL реализована встроенная поддержка zlib и по умолчанию включена компрессия пересылаемых пакетов данных. Причем при сжатии очередного пакета используется информация об уже сжатых и отосланых пакетах. Например, если вы отсылаете три одинаковых пакета по 400 байт, и первый пакет сжимается до 275 байт, то второй и третий пакеты в сжатом виде займут всего по 12 байт. Такой подход очень экономит трафик, но применим только к протоколу TLS, потому что он гарантирует доставку пакетов и сохраняет последовательность, в которой пакеты были отосланы. Понятно, что для протоколов с негарантированной доставкой такой подход к сжатию не может быть применён. Это подтверждает RFC 3749 "TLS Compression Methods", в котором написано следующее:

Some compression methods have the ability to maintain state/history information when compressing and decompressing packet payloads. The compression history allows a higher compression ratio to be achieved on a stream as compared to per-packet compression, but maintaining a history across packets implies that a packet might contain data needed to completely decompress data contained in a different packet. History maintenance thus requires both a reliable link and sequenced packet delivery.

Протокол DTLS, выбранный для нашего проекта, был разработан специально для UDP и не гарантирует доставку пакетов. Но большей частью он основан на TLS, и по идее реализация протокола DTLS должна учитывать требования RFC 3749.

К сожалению, в OpenSSL этот момент не учитывается и в DTLS сжатие используется точно также, как и в TLS. Совершенно очевидно, что при потере одного из пакетов вся передача будет нарушена, и даже если этот пакет придет позже, то восстановить исходную последовательность принимающая сторона уже не сможет. Такой неприятный момент должен решаться простым отключением компрессии при использовании DTLS, однако в OpenSSL 0.9.8 это превращается в проблему, потому что в API просто нет функции отключающей компрессию. Вот цитата из документации на метод SSL_COMP_add_compression_method:

An OpenSSL server will match the identifiers listed by a client against its own compression methods and will unconditionally activate compression when a matching identifier is found. There is no way to restrict the list of compression methods supported on a per connection basis.

Начиная с OpenSSL 1.0.0 добавлена опция SSL_OP_NO_COMPRESSION, которая делает то, что мне нужно, но в версии 0.9.8 ее нет. В поисках решения я порылся в сети, но ничего внятного, кроме вот этого не нашел:

void disable_openssl_compression()
{
  STACK_OF(SSL_COMP)* comp_methods = SSL_COMP_get_compression_methods();
  sk_SSL_COMP_zero(comp_methods);
}

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

Между тем, изучив исходный код OpenSSL, я обнаружил, что информация о доступных компрессорах хранится в простом списке, в который можно добавлять свои компрессоры с помощью функции SSL_COMP_add_compression_method. А раз можно добавлять, то значит должен быть способ и удалять элементы списка. Такой функции в OpenSSL API нет, но после изучения исходников функции SSL_COMP_add_compression_method можно написать свою, не пользуясь хаками, а только предоставленным API:

void disable_openssl_compression (void)
{
  int n = sk_SSL_COMP_num(SSL_COMP_get_compression_methods());
  for (int j = 0; j < n; ++j)
  {
    SSL_COMP *comp = sk_SSL_COMP_pop(SSL_COMP_get_compression_methods());
    OPENSSL_free(comp);
  }
}

Комментариев нет:

Отправить комментарий