суббота, 30 июня 2012 г.

Уязвимости серверов к медленному чтению

По мотивам старой статьи на habrahabr.

Когда я писал echo_server, этой статьи на Хабре еще не было. Но я думал о том, что клиент (remote peer) может «нарочно медленно» принимать данные. Именно поэтому я использую класс ma::cyclic_buffer, совмещающий в себе и буфер приема, и буфер отправки. Благодаря такому совмещению, мне, кажется, удалось более гибко распределить ресурсы сервера:
  • с одной стороны, я выставляю ограничение памяти (размер буфера) на всю сессию,
  • с другой – это ограничение динамически распределяется на буфер отправки и буфер приема одновременно, т.е. выделенное пространство используется максимально эффективно.
Кроме того, я реализовал таймаут. И не просто таймаут на отправку (хотя хватило бы и его), но совмещенный таймаут (+ сэкономил один таймер) «активности remote peer»:
в течение указанного для таймаута значения удаленный конец должен завершить хотя бы одну активную операцию ввода/вывода, если только таковая есть (еcли кому-то интересны подробности, спрашивайте в комментариях).
Конечно, это не полноценная защита. Но, IMHO, и сервер (middleware, application layer) надо проектировать так, чтобы минимизировать возможность проведения успешных атак (в т.ч. DOS) – в идеале вообще исключить их.

13 комментариев:

Nikki Chumakov комментирует...

Вопрос не совсем по теме, но по производительности.

По мотивам этой ветки: http://comments.gmane.org/gmane.comp.lib.boost.asio.user/5133

Простой echo-server на ерланге уделывает echo server на asio.

Erlang сервер: https://github.com/virtan/mrps/tree/master/erlang-virtan

Тестировалось этим клиентом: https://github.com/virtan/mrps/tree/master/client-c++-virtan

на i7 2600 с 4 ядрами под linux и boost 1.50. Оценивалось количество успешно переданных/принятых сообщений с большим кол-вом сессий. На латентность не обращалось внимание.

Попробовал накидать свой клиент, с использованием io_service per CPU и pthread_setaffinity и с custom handler allocator. Тексты выложу немного позже, если кому-нибудь интересно.

Но что странно, похоже что ни service-per-cpu, ни handler allocator на этом тесте не влияют на производительность. Об этом же писал Christof Meerwald, в упомянутой ветке.

И резервов догнать ерланг, как я понимаю, нет.

Марат Абраров комментирует...

По мотивам этой ветки: http://comments.gmane.org/gmane.comp.lib.boost.asio.user/5133

В той ветке, насколько я помню, сошлись в том, что Asio, возможно, неэффективно работает с epoll, что, в свою очередь, выливается в излишние переключения контекстов потоков.

Простой echo-server на ерланге уделывает echo server на asio.

Хотелось бы глянуть на "echo server на asio".

И резервов догнать ерланг, как я понимаю, нет.

Покажите результаты, пожалуйста.
Есть один нюанс, который стоит всегда помнить - custom handler allocator должен влиять на скорость выполнения операций ввода/вывода. И только. Создание новых экземпляров session при помощи ::new может быть узким местом сервера на Asio (по сравнению с Erlang). Т.е. в тесте стоит все же выделить результаты для собственно ввода/вывода на уже установленных соединениях.
Например, при тестировании echo_server из asio-samples, я устанавливаю заранее большое число recycled_sessions, после чего «прогреваю» echo_server созданием большого кол-ва одновременных сессий (да, echo_server пока просто не умеет преаллоцировать экземпляры session).

Тестировалось этим клиентом: https://github.com/virtan/mrps/tree/master/client-c++-virtan

А asio_performance_test_client не пробовали? У меня упирается в пропускную способность сети (а запускать сервер и клиент на одной машине для теста бесполезно).

Марат Абраров комментирует...

Но что странно, похоже что ни service-per-cpu, ни handler allocator на этом тесте не влияют на производительность.

Эх, еще бы результаты работы профилировщика глянуть.

Nikki Chumakov комментирует...

Исходники сервера: http://liveworkspace.org/code/91a89b2fd2f894af13c6282e3ddde82f

Код не академический, заранее извиняюсь.

Смогу перетестировать и покопаться профайлером на следующей неделе.

Nikki Chumakov комментирует...

Попробовал asio_performance_test_client, на большом количестве сессий (100к) он тормозит.

На небольшом (100) показывает лучшие результаты, но в любом случае echo server с ним грузит машину на 10% (показывает 90% idle). При использовании клиента из github - загрузка значительно сильнее 65% (35% idle).

Предполагаю, что из-за использования мутексов/локов вместо атомарных операций.

С остальным пока продолжаю разбираться.

Марат Абраров комментирует...

Попробовал asio_performance_test_client, на большом количестве сессий (100к) он тормозит.

Одновременно 100k исходящих TCP-соединений с одной машины - такое возможно (ephemeral port)? Попробуйте, хотя бы, 30k. Иначе, боюсь, все, что свыше определенного лимита, будет вхолостую отнимать время процессора на циклы async_accept -> handle_accept(some_error_related_to_resource_out).

В чем выражаются тормоза?

При использовании клиента из github - загрузка значительно сильнее 65% (35% idle). Предполагаю, что из-за использования мутексов/локов вместо атомарных операций.

Явная блокировка на мьютексе в asio_performance_test_client происходит лишь при закрытии TCP-соединения. Значит, либо сервер часто рвет соединения, либо тормозит asio::io_service::strand (там внутри тоже мьютекс), либо дело вовсе не в блокировках (частые переключения контекстов потоков?).

Мне все больше хочется увидеть ваш отчет профилировщика. Попробую провести эксперимент на домашней машине с профилировщиком, встроенным в MS VS 2010 - горячие точки, полагаю, можно найти и так. Правда, это будет Windows-only.

В любом случае, спасибо за информацию.

Nikki Chumakov комментирует...

Открыть >64k сессий между двумя машинами несложно. Для этого надо назначить на сервере несколько ip адресов на сетевой интерфейс, а с клиента коннектится на них по-очереди.

Но в данном случае я ошибся, речь идет о 10k сессий.

Торомозит - это значит недозагружает эхо сервер.

Я сейчас взял пару серверов E5645 c 12 физ. ядрами, с HT всего 24 логических ядер. Соединены сетью напрямую. На одной запущен эхо сервер, на другой пускаю разные клиенты.

Клиент из github открывает 10k сессий к серверу, отправляет по ним сообщения по 32байта, принимает, сравнивает.

За минуту работы он у меня показывает типично 40-45 млн успешно принятных сообщения. Т.е. получается 1280-1440 MByte в минуту.

При этом на машине с echo сервером возникает суммарная загрузка процессоров
User: 8.5%, System: 35%, I/O: 16.5%, Idle: 40%

На машине с клиентом:
User: 16.5, System: 59%, I/O: 20%, Idle: 4.5%

Запускаю твой клиент
asio_performance_test_client 10.0.0.2 32000 24 32 100 60

он выдает
275390944 total bytes written
275389520 total bytes read

Загрузка машины с эхо сервером -
User: 4%, System: 16%, I/O: 2%, Idle: 78%.

На клиенте:
User: 14%, System: 75%, I/O: 10%, Idle: 1%.

До профилирования я пока не добрался.

Сервер соединения не рвет, насколько я могу судить. Сетевых ошибок вообще не возникает.

Nikki Chumakov комментирует...

Попрофилировал сегодня с помощью Intel VTune. Завтра еще продолжу.

На первый взгляд есть проблема с клиентом asio-samples заключающаяся в недостаточном распараллеливании. Возможно виноват asio::strand.

Анализ (overview) выполнения клиента из asio samples:
http://i081.radikal.ru/1207/72/38915064a525.png

http://i038.radikal.ru/1207/50/a317e304fcd2.png

И, для сравнения, клиента из github:
http://i024.radikal.ru/1207/b4/66195a49f1c5.png

http://s019.radikal.ru/i610/1207/d3/7140349ebaa2.png

Сравнить работу echo серверов пока не успел.

Марат Абраров комментирует...

На первый взгляд есть проблема с клиентом asio-samples заключающаяся в недостаточном распараллеливании. Возможно виноват asio::strand.

Возьмите обновленный вариант из https://asio-samples.svn.sourceforge.net/svnroot/asio-samples/trunk. Есть надежда, что виноват вызов конструктора asio::io_service без указания concurrency_hint.

Я же тем временем смотрю, что творится с echo_server и asio_performance_test_client под Windows. И, насколько я пока могу судить, там все хорошо и ожидаемо.

Марат Абраров комментирует...

Добавлю: в Asio под Linux в качестве мьютекса используется класс boost::asio::detail::posix_mutex (boost_1_50_0\boost\asio\detail\posix_mutex.hpp). В то же время, под Windows используется критическая секция (boost_1_50_0\boost\asio\detail\win_mutex.hpp).

Не знаю как в POSIX Threads, но критическая секция Windows помимо мьютекса содержит в себе spin lock, который не дает работать с реальным мьютексом Windows пока кол-во итераций на spin lock не достигнет определенного барьера. Полагаю, это (вместе с IOCP с его специализированной процедурой планирования потоков) позволяет Asio под Windows избавиться от того громадного кол-ва переключений контекстов потоков, что я наблюдаю в http://i038.radikal.ru/1207/50/a317e304fcd2.png.

Марат Абраров комментирует...

Посмотрел заодно Boost.Thread и обнаружил следующее:
1. boost::mutex под Windows использует spinlock + Windows event, что очень похоже на обычную критическую секцию Windows, но позволяет делать timed-блокировки.
2. boost::mutex под Linux использует только POSIX Threads mutex, что в купе со статьями навроде этой вызывает недоумение.

Марат Абраров комментирует...

Кстати, вот что получилось у меня на Windows (Intel Core2 Duo E7200, клиент по сети).

Марат Абраров комментирует...

Забыл указать в предыдущем комментарии - это результаты echo_server.