Наверх ▲

Как не надо писать приложения, основанные на протоколе TCP

Артём Гавриченков Артём Гавриченков Ведущий разработчик в компании "Highload Lab".

Артём Гавриченков: Добрый день. Меня зовут Артем Гавриченков. Я – сотрудник "Highload Lab". На протяжении последних двух лет в основное рабочее время я занимаюсь разработкой сети фильтрации трафика "Curator". Это то, что защищает от DDoS-атак, в том числе. Так и возникла идея доклада.

Доклад "основан на реальных событиях". Предупреждаю: сейчас я не рекламирую наш продукт. Просто говорю о том, что мы его написали сами. Все протоколы, используемые внутри приложения, написаны нами. За два года мы видели очень много по-разному настроенных и по-разному написанных веб-приложений (включая специальные веб-серверы, трекеры и массу всего другого). Множество веб-браузеров и разных ботов – хороших и плохих.

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

Какого рода бывают проблемы?

Нужно помнить о том, что протокол TCP написан так, что он изначально не обеспечивает гарантированную доставку сообщения.

Три года назад у меня был один случай. Я работал в компании "Hewlett Packard". В какой-то момент я обнаружил, что на том же этаже сидят люди, которые занимаются продажами и развитием операционной системы "OpenVMS". Мне стало интересно, что это такое. Я пригласил их рассказать нам, что происходит в "OpenVMS", как она развивается. Оказалось, что это достойные люди, отлично разбирающиеся в своем деле.

В какой-то момент они сказали: "Как всем известно, гарантированную доставку сообщений TCP не обеспечивает". Выяснилось, что в понимании "OpenVMS" TCP действительно не обеспечивает гарантированную доставку. В понимании "OpenVMS" гарантированная доставка – это доставка за гарантированное время. Такого в TCP нет. Он не поддерживает работу в реальном времени.

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

Нужно помнить, что бездействие пользователя – это тоже действие. В ряде случаев (если это, допустим, атака) – бездействие может быть намеренным.

  • Время, которое пользователь проводит на сайте или в вашем приложении (если речь идет не о сайте).
  • Время между получениями данных от пользователя.
  • Время между запросами (запрос – это несколько операций получения отправки).
  • Время запроса целиком.
  • Время сессии.
  • Время соединения.
  • Время любой проектируемой сущности должно быть ограничено и конфигурироваться.

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

Отсюда следует вывод. Часто используемый везде (не только в веб-приложениях) "long polling" (метод работы, при котором клиент подключается к серверу и "висит" там до получения события) – приемлем, если у вас гигантское "облако". В противном случае вам нужно протестировать применение этого метода заранее, поскольку на практике часто оказывается, что вам по неизвестной причине не хватает ресурсов.

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

Наверное, все уже знакомы с атакой Slowloris (Apache). Большинству, думаю, знакома и атака Slow HTTP POST. Многие даже считают, что Nginx для нее неуязвим. Здесь имеет место небольшая подмена понятий. Дело в том, что Nginx неуязвим к атаке Slow HTTP POST при формулировке "DDoS сервера с одного компьютера".

Но DDoS Nginx с ботнета методом Slow POST – это рабочий вариант. К сожалению, в Nginx до сих пор нет (неделю назад точно еще не было, насколько я смотрел) возможности ограничить время бездействия, которое пользователь может провести между двумя отосланными, допустим, байтами поста. Таким образом, довольно легко можно получить исчерпание ресурсов на сервере. Будет обидно.

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

Поговорили об одном аспекте временно-пространственного континуума, перейдем к другому.

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

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

Довольно часто встречается такая ситуация. Допустим, есть некоторый сайт, который занимается аукционами. Было поставлено следующее требование к разработчикам: пользователь должен иметь возможность загружать файлы любого объема. От такого подхода сайт довольно быстро "сложился", поскольку там из-за серии из 6 авторизаций была возможность с 6 аккаунтов "заливать" туда разную ерунду.

Если вы хотите давать пользователям возможность загружать файлы, и при этом вы не "YouTube", то задумайтесь о том, какого размера эти файлы могут быть. Если вы хотите получать от пользователей текстовые документы, то их размер точно меньше 500 мегабайт. Не нужно требовать от разработчиков поддержки больших объемов.

Еще один важный факт.

Если вы работаете с TCP-сокетами (например, "никсовыми"), то довольно часто возникает ошибка (когда пишут приложение на "Си", допустим). Если соединение закрылось – ОК, оно закрылось, мы больше ничего не ждем. На практике соединение может закрыться по тысяче причин. В ряде случаев вас не устроит факт того, что это произошло.

Вам придется как-то это доработать. Допустим, перезапросить данные, перепослать данные, написать в лог. Словом, сделать что-нибудь. Вроде бы, тривиальная вещь, но лучше всегда проверяйте ошибку.

У нас есть одна программа, на основе которой можно изучить большинство ошибок сетевого программирования – к сожалению, это "Internet Explorer".

Однажды произошла неприятность, с которой мы справились исключительно методами администрирования. Был некоторый сайт, который раздавал exe-файлы. В ряде случаев они доставлялись "битыми". Мы начали разбираться и выяснили следующее: "Internet Explorer" соединяется с сайтом и начинает скачивать файл. Допустим, он получает данные и говорит: "Все хорошо", на протяжении 10 минут. Файл при этом имеет объем 3,5 МБ.

Скачав примерно 1,5 МБ, "Internet Explorer" начинает непонятно себя вести. Возможно, ему не хватает каких-то ресурсов. Возможно, он отправляется куда-то. Допустим, не выделил буфер или идет искать память, а в это время говорит серверу: "Сейчас". Отсылает TCP-сегмент с "window size=0". Просит: "Не посылай мне ничего, пожалуйста, пока. Мне памяти не хватает. Я сейчас найду ее и вернусь". Сервер говорит: "ОК".

Проходит 1 секунда, 10, минута. Ресурсы не нашлись. Window size=0. На каждый временной промежуток есть ограничение. После минуты соединения с нулевым окном ожидание прекращается. Да, это вполне рабочий метод атаки. Подключиться, выставить нулевое окно, израсходовать одно соединение и делать так дальше.

В этот момент сервер посылает на клиента (там Windows, "Internet Explorer") "connection reset". TCP-сегмент с флагом RST. В этот момент "Internet Explorer" определяет: "О! Соединение закрылось. Значит, файл скачался, все".

Претензии были к нам. Но, слава Богу, клиент попался адекватный. Он принял наши объяснения и теперь советует своим пользователям использовать "Firefox" и "Chrome". Не во всех случаях у вас получится так удачно. Если вы допустите эту ошибку, то клиенты, которые к вам подключены, обновятся не мгновенно. Поэтому нужно учитывать это сразу. Хотя, казалось бы, это банальная вещь.

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

Но ресурсы также могут быть ограничены "снизу". Мы не раз наблюдали защищенные веб-серверы, которые были зажаты "снизу" настолько, что в какой-то момент при подключении им не хватало, допустим, соединения. Там был установлен жесткий лимит. Хотя куратор подключает всего с пяти точек. Там есть лимит на количество подключений с одного IP-адреса, мы всегда рекомендуем убирать его. К сожалению, не все слушаются.

Если у сервера нет ресурсов, то соединение повиснет в каком-то из состояний ожидания. В самом худшем случае вы об этом даже не узнаете. Если у вас не хватит соединений или какого-то другого ресурса, возможно, даже в логах у вас ничего не будет. Здесь куратор, как правило, выступает "лакмусовой бумажкой". Когда сервер ставится за него, мы сразу видим все проблемы, которые испытывает клиент.

В ряде случаев оказывается, что владелец ресурса даже не подозревал о том, что эти проблемы есть. Он нашел какое-то руководство по настройке конфигурированию Nginx Apache, "слизал" оттуда все конфигурационные переменные, все значения, и жил себе спокойно, не зная, что он теряет клиентов.

Еще одна важная вещь. Она относится, в первую очередь, к тем, кто реализует свой собственный протокол (допустим, игровой сервер). Если у вас игровой сетевой протокол, то вы должны учитывать, что процессор "тройного рукопожатия" (SYN – SYN – ACK – ACK) занимает довольно приличное время. Это время ваши пользователи (допустим, игроки) будут видеть у себя как задержки. В теории эта задержка небольшая, но для геймеров она очень принципиальна.

У нас были случаи, когда из-за "падения" сетевого подключения где-то в районе "Ростелекома" до нашей точки около двух секунд не проходили соединения. В итоге на форуме появились посты о том, что "Curator" снова "тормозит". На самом деле, он не "тормозит" как раз потому, что между защищаемым сервером и сетью фильтрации поддерживается постоянное соединение HTTP/1.1.

Поэтому все запросы, которые приходят к нам, проигрывают "рукопожатие" только с клиентом. С сервером оно уже было. За счет этого в ряде случаев даже получается ускорение (которое мы, правда, никогда не гарантируем). Если у вас есть возможность сразу предусмотреть в своем протоколе постоянные соединения – пожалуйста, сделайте это, иначе потом вам придется писать вторую версию и "городить костыли".

Историческая справка о "костылях"

К большинству протоколов, изначально основанных на модели "подключился – получил данные – отключился", потом были написаны расширения. Какие-то из них были признаны неудачными. Transactions TCP RFC 1644 был экспериментальным, а сейчас он считается устаревшим. Но само наличие такого "костыля" не так давно заставило нас принять ошибку в "линуксовом" ядре за нормальное легитимное поведение и считать его таковым на протяжении, по-моему, нескольких месяцев.

Из этого можно сделать некоторый вывод. Ошибка была связана вот с чем. Если на линуксовую машину приходит TCP-сегмент с выставленными флагами и SYN, и FIN, то "Linux" до определенной версии ядра (до декабря) считает это легитимным запросом на открытие соединения.

Если у вас на сервере установлен RAID-лимит по открытию соединений в IPTables, то он легко проходится просто отсылкой "SYN – FIN". Мы это нашли довольно давно. У нас специфика работы такая: если кто-то что-то находит в TCP-стеке (допустим, "линуксовом"), мы об этом довольно быстро узнаем. Но до определенного момента мы считали, что это просто RFC так реализован. Недавно это поправили. Так что, если у вас такой лимит используется, например, в IPTables, обновите ядро или исправьте правила.

Оптимизация

Мы уже начали говорить об оптимизации. Оптимизация – это, конечно, хорошо, если она не преждевременная. Мы для себя вывели три основных пункта.

1. Перед тем как оптимизировать, обязательно нужно все измерять. В отдельных случаях стоит взять в руки профайл и посмотреть, где проблема. Она, как правило, оказывается не там, где вам кажется.

2. Перед тем как начать решать проблемы за счет оптимизации, убедиться, что проблема действительно есть, а не позаимствована из умных книжек.

3. Наконец, третий важный пункт. Это в большей степени относится к веб-программистам. Если ваш сайт замечательно написан и быстро работает, сэмулируйте, пожалуйста, потерю пакета между сервером и своим браузером. Это довольно легко делается средствами IPTables (можете это "нагуглить", я не стал вставлять это в презентацию).

Зачем это нужно делать?

Допустим, вы разрабатываете сайт на своем стомегабитном или гигабитном канале. Потом щелкаете ссылку, и куда-то "улетает" 5 МБ JSon, который потом разворачивается, и по AJAX "пролетает" обратно какой-то ответ в XML. У вас это может работать достаточно быстро.

Вы увидите, что будет, если между вами и клиентом встанет некоторая сеть фильтрации или любой прокси-сервер.

Наконец, вы почувствуете разницу в скорости, если уедете в деревню с "Yota" и попробуете открыть сайт там. Ваши пользователи в этом случае будут испытывать проблемы. Поэтому всегда проверяйте скорость в экстремальных случаях.

Еще одно замечание касательно оптимизации

Преждевременной оптимизации делать не надо. Часто люди проектируют протокол и говорят, что текстовые данные тяжело передавать. Их много, в конце концов. "Там слишком много слов. Давайте бинарник пересылать!"

Чего нельзя добиться с таким подходом?

С одной стороны, как выясняется, они не получают большого прироста скорости. Проблема со скоростью оказывается где-то еще. С другой стороны, при отладке с помощью "TCP dump" люди начинают рвать на себе волосы. Начинают писать какие-то "обертки", которые позволяют "вынуть" из "TCP dump" то, что там закодировано. Но это тратится время, и, как правило, это не нужно.

В конце концов, всегда возможно "завернуть" ваши данные во что-либо типа BSon (бинарный аналог JSon). Или "Google Protocol Buffers". Мы, в частности, вторую вещь пробовали. Она вполне юзабельна, пользуйтесь.

Каким образом можно оптимизировать серверное приложение, использующее TCP?

Уже, наверное, многие знают, что у "линуксового" TCP-сокета есть такая опция – TCP_NODELAY. А в RFC написан алгоритм Нейгла. 

В чем его суть? Как сам Нейгл писал в свое время в рассылке, суть алгоритма в том, чтобы в любой момент в сети находился только один сегмент данных, который "летит" (отправлен, но не принят, допустим).

Что это дает? Если вы, допустим, присылаете много данных подряд малыми порциями (буквально по байту отправляете), то при отключенной опции TCP_NODELAY TCP-стек сначала соберет все ваши пакеты в один сегмент и отправит его. В ряде случаев это не нужно.

Кроме того, иногда (допустим, с DELAY ACK, как писал сам Нейгл) это работает плохо, поскольку вводит таймауты до полусекунды, а то и больше. Поэтому, если вы пересылаете данные малыми порциями, включите NODELAY, чтобы сегменты отправлялись сразу.

Есть только одно исключение. Например, если ваше приложение построено таким образом, что оно отправляет один и тот же массив данных, но формирует его в два шага. Вы делаете ответ на HTTP-POST из приложения, и сначала формируете (и отправляете) заголовок запроса, а потом – тело. Их полезнее будет объединять в одно.

Для этого есть техника, которая называется "corking". В "Linux" есть опция TCP_CORK, которую можно "повесить" на сокет. После этого можно отправить туда все данные, которые должны отправиться одним пакетом. Потом обратно "повесить" TCP_NODELAY.

TCP_NODELAY, TCP_CORK. Положили данные. TCP_NODELAY. Когда выполняется последний "getsockopt", все данные, которые остаются в буфере, тут же отправляются. Это то, что вам надо. Это не ерунда с бинарниками и бинарными протоколами против текстовых протоколов. Это дает реальное ускорение, видимое глазу.

Еще об ускорениях, которые видны невооруженным глазом

В "Linux" (и даже в Windows) автоматически присутствуют определенные параметры TCP. Во-первых, нужно помнить, что TCP-соединения – это некоторый автомат, по которому мы  с некоторыми задержками переходим из одной точки в другую. Кроме того, на каждое состояние этого автомата выделяется какой-то объем памяти.

Они все настраиваемые. Если у вас стомегабитное соединение сервера, то вы не увидите большой разницы по сравнению с настройками по умолчанию. Если у вас гигабит или 10 гигабит, то тюнинг параметров "tcp_rmem", "tcp_wmem" даст вполне ощутимый прирост.

Кроме того, в net.ipv4, net.cor есть несколько таймаутов, которые тоже важны. Про них мы говорили на "Highload" пару лет назад. TCP_fin_timeout нужно обязательно тюнить.

Там есть одна странная опция под названием tcp_max_orphans. Она довольно интересна в том смысле, что не вполне отвечает своему изначальному значению. И даже названию. Это максимальное количество сокетов, находящихся в так называемом orphan-состоянии. Если таких сокетов слишком много, вы получите сообщение "out of socket memory" в приложении, и будете к этому не готовы.

Коварство этой опции заключается в том, что вы можете выставить для нее значение в 65 тысяч. Но в ряде случаев "линуксовое" ядро легко разделит это значение на четыре и будет считать это максимумом.

Поэтому, если вы получаете "out of socket memory" при тестировании, то, в первую очередь, посмотрите на то, сколько у вас orphan-сокетов. Чтобы не сидеть долго, можно сразу поставить довольно высокое значение.

Как я и говорил, в Windows во "FreeBSD" такие параметры тоже есть. Просто я не так подробно их разбираю.

Одной строкой напомню то, что уже, казалось бы, все должны знать. Оказывается, об этом помнят не все.

Во-первых, если у вас серверное приложение, то оно "висит" на некотором сокете, "слушает" сокет. Когда вы его перезапускаете, оно пытается "слушать" тот же самый сокет. Как правило, он все еще занят, поскольку находится в тот момент в состоянии FIN-WAIT. Для этого в "Linux" есть опция REUSEADDR. Если у вас "слушается" сокет, обязательно "вешайте" ее туда, чтобы при перезапуске приложение продолжало "слушать" тот же сокет.

Используйте "sendfile". Его специально написали для отправки файлов, для передачи данных из одного дескриптера в другой. Оно сделано для ускорения. Оно помогает. Пользуйтесь.

Про select poll/epoll, думаю, говорить никому не надо.

Следующий пункт вполне тривиален. Выясняется, что есть такое свойство ЭВМ, как "endian", описывающее, в каком порядке будут идти байты в машинном слове. На разных платформах оно бывает разное. Поэтому, если вы пишете что-то совсем свое, то вам нужно прочитать руководство по "n_to_h_s". Желательно выучить эти названия наизусть.

Еще одна важная вещь. Целочисленные переменные имеют разный размер. Если вы на одной машине пакуете куда-то "long", то на другой машине вы этот "long" можете оттуда не разобрать, и сегментирование не удастся. Нужно это учитывать.

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

Допустим, у вас есть свои настройки для IPv4 (proxies.net). Заранее предусмотрите IPv6, поскольку в последнее время появилась такая вещь, как "Accidental IPv6 Deployment". Это случается, когда вы только разворачиваете сервер. На нем сразу "поднимается" IPv6, который есть у хостера. Вы внезапно начинаете еще и по нему работать.

Постарайтесь сделать так, чтобы при настройке сервера вы твердо знали, используется в данный момент IPv6 или нет. Если используется, то стоит определиться нужно это вам или нет. Если нужно, то предусмотрите для него те же настройки. Не "забивайте" на него. Если вы еще не знаете про IPv6, то злоумышленники уже про него многое знают.

На этом все. Давайте перейдем к вопросам, дискуссии и обсуждениям.

 

Вопросы и ответы

Реплика из зала: Спасибо за доклад. У меня такой вопрос. Я не услышал ничего про буферы приема на той стороне. В моей практике это одна из наиболее очевидных проблем, из-за которых "тормозит" передача данных через Интернет.

Артем Гавриченков: Это вопрос?

Реплика из зала: Вопрос – почему вы не коснулись этой темы?

Артем Гавриченков: Потому что мы с этой проблемой не сталкивались. Ни в своем приложении, ни у клиентов – ни разу.

Реплика из зала: Здравствуйте. Спасибо за доклад. Такой вопрос. Был ли у вас какой-то специфический опыт с плохим мобильным соединением типа убогого GPRS у клиентов? Какие там возникают проблемы, по вашему опыту, если он есть?

Артем Гавриченков: Проблемы возникают, в частности, со временем жизни соединения. У нас такие проблемы были года полтора назад, когда мы слишком "зажали" таймаут. Люди, работающие с телефона, жаловались. Но тогда еще EDGE не было, по большому счету. В основном, проблемы только с таймаутами. Но, как я уже говорил, нужно тестироваться, в том числе, на медленном соединении. Если тестироваться на медленной скорости соединения, проблем не возникнет.

Реплика из зала: У меня вопрос по поводу HTTP POST атаки на Nginx. Вы какой таймаут не нашли?

Артем Гавриченков: Я отослал байт POST первый и жду, допустим, секунд пять. Потом посылаю второй. "Content-Length" при этом задекларирована, допустим, мегабайт. Соответственно, это один миллион умножить на пять миллионов секунд, получается.

Реплика из зала: Есть такая директива, называется "client body timeout". По умолчанию 60 секунд. Можете поставить одну.

Артем Гавриченков: Насколько я помню, "client body timeout" (насколько я помню руководство, хотя вы, конечно, помните его лучше) рассчитан на то, что я выставляю 60 секунд, отсылаю первый байт и жду 61-у секунду. Вот тогда у меня случился разрыв соединения.

Реплика из зала: Да.

Артем Гавриченков: А я не попадаю на "client body timeout", поскольку я через пять секунд отсылаю второй байт. А третий – еще через пять.

Реплика из зала: Да.

Артем Гавриченков: Что, в этом случае случится разрыв соединения?

Реплика из зала: Нет. Допустим, вы хотите послать пять мегабайт. Ставьте. Если вы считаете, что у вас возможна такая атака, то можете поставить пять секунд для таймаута.

Артем Гавриченков: Как сейчас это реализовано в "Curator"? У нас таймаут пропорционален размеру собственно присылаемых данных. На мегабайт один таймаут, на сто мегабайт – другой. Возможности задать его я не видел. Если задавать пять секунд на "body timeout" при размере файла, допустим, 20 мегабайт, то успеют только люди с соединениями, которых у меня, например, нет.

Реплика из зала: Но с чего вы взяли, что байты файла размером в 100 МБ будут идти чуть-чуть быстрее или медленнее, чем байты файла размером 1 МБ? Я не понимаю. Почему вы взяли такую эвристику и считаете, что она правильная?

Артем Гавриченков: Потому что она работает.

Реплика из зала: Допустим, у меня потоковая передача. 256 килобайт в секунду загружают. Я закачиваю 1 мегабайт или закачиваю 100 мегабайт. У меня таймауты будут разными между пакетами?

Артем Гавриченков: Скорее всего, нет. С чего бы им быть разными между пакетами?

Реплика из зала: А логика где? Если таймауты одинаковые? Допустим, вы там поставили какую-то эвристику...

Артем Гавриченков: Имеется таймаут как на время между двумя полученными пакетами, которые содержат данные, так и на весь POST-запрос.

Реплика из зала: Вы ставите таймауты на весь POST-запрос? Хорошо. Допустим, пользователь что-то "качал" быстро, а потом у него стало медленно "качаться". Или наоборот. Как быть в этом случае?

Артем Гавриченков: Если у него начало медленно "качаться"? Если таких пользователей, у которых стало медленно "качаться", много, то у ряда из них произойдет connection reset.

Реплика из зала: Соединение закрывается?

Артем Гавриченков: Только если (я подчеркиваю – если) таких пользователей много. Если к нам пришло 20 человек, и все они медленно качают файл, расходуют ресурсы сервера (или, например, выставляют окно в ноль или единицу), то у них через некоторое время соединение прервется.

Мы можем взять, допустим, 10-20 тысяч машин (да, не у всех много ресурсов). Можем пойти на какой-нибудь довольно дешевый сервер на "Masterhost" из десяти тысяч машин. Можем попытаться слить файл через POST, или "слить" через GET и загрузить через POST, неважно. При этом можно отправлять, допустим, по одному байту.

Есть один важный момент касательно этого. Дело в том, если такие люди придут и начнут "закачивать" файл, то, как вы помните, Nginx запишет все в лог только после того, как соединение закроется, и запрос будет отработан. Получается анекдотическая ситуация. У человека 10 тысяч соединений, которые расходуются непонятно на что и все висят, а в логах ничего нет.

Реплика из зала: Спасибо.

Реплика из зала: У меня не вопрос, а вопрос-коментарий. У "Google" есть такая замечательная инициатива, абсолютно корректная и правильная с точки зрения веб-разработчика – отсылать первый сегмент данных на ресивер вместе с sin-пакетом.

Артем Гавриченков: Да.

Реплика из зала: Соответственно, сразу срубаем, как минимум, одно RTT на реакцию обработки запроса. Как быть с точки зрения DDos? В смысле, есть что посоветовать "Google"?

Артем Гавриченков: Что посоветовать "Google"?

"Google" можно посоветовать жить хорошо, поскольку у "Google" ресурсов хватит, это заранее известно. У кого их не хватит, у тех будут проблемы.

Думаю, все здесь понимают, что при размерах дата-центра "Google" у него с этим проблем не будет. А вот у вас – вполне вероятно, из-за самой сути TCP… Перед тем как передавать друг другу данные, вы производите вырожденный процесс аутентификации. Если вы с sin-запросом сразу присылаете данные, то с тем, чтобы сгенерировать такой вот "spoof" с произвольным размером пакета, никаких проблем нет.

Поэтому "Google" с этим справится, у него каналы большие. А вот рядовой сервер, к которому хотя бы 100 гигабит подключено, вряд ли. Я ответил на вопрос? Ок.

Реплика из зала: К предыдущему вопросу. Я не очень понял следующую ситуацию. С sin-пакетом никто не может запретить сформировать произвольный пакет. Правильно?

Артем Гавриченков: Вообще никто не может запретить сформировать произвольный пакет.

Реплика из зала: Соответственно, таким образом, отослав пакет с большой порцией данных…

Артем Гавриченков: DDP, например. Вы об этом?

Реплика из зала: Дата-центр все равно должен будет его принять. Поэтому в данном случае проблема дата-центра не "Google" это или не "Google", а тот, кто совершает атаку. Он может сформировать пакет и таким образом сделать DDoS-атаку на любой сервер.

Вопрос, скажется ли это положительно на установлении соединений, если передавать пакет? Чаще всего TCP-соединение устанавливается нормально. Будет ли ускорение при передаче с началом пакета данных?

Артем Гавриченков: Во-первых, очень важный факт: сформированный пакет действительно может быть любым. Не любой до сервера дойдет.

Дата-центр может вообще не доводить до вашего сервера определенные типы пакетов, если вы их можете указать заранее. Во-вторых, многие героические, величайшие люди этой страны уже используют BGP FlowSpec, с помощью которого вы можете, допустим, автоматически закрыть себе произвольно сформированные пакеты.

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

Возможно, не удастся справиться еще в ряде других случаев. Но так есть шанс, что, по крайней мере, эта атака мимо вас пройдет. А так – не пройдет. Я ответил на вопрос?

Реплика из зала: Да. Спасибо.

Артем Гавриченков: Кстати, мне вспомнился недавний анекдот из реальной жизни. Про размеры буфера, опять же. Есть такая утилита – "ngrep". Она использует внутри себя "libpcap". Она у нас в сетевом отделе довольно широко используется для мониторинга, сетевые инженеры ее применяют.

Не так давно эта часть мониторинга взяла и "отвалилась". На ней ничего сверхважного не было. Но было чрезвычайно обидно. При этом он сыпала ошибками из серии "out of socket memory", и все.

Если вы помните, "libpcap" может получать разный объем пакета. В "TCP Dump" опция "dash – as", например. По умолчанию там, по-моему, 56 байт. Довольно странное значение. Или больше. Короче, оно небольшое. А вот "ngrep" по умолчанию его выставляет в 65K. Ему же много данных надо.

Никаких проблем с этим у нас раньше не было. Вчера появились. Секрет в том, что мы недавно пересмотрели то, как меняем ядерные модули. Теперь они меняются практически "на лету", и перезагрузка сервера и точки не требуется – недоступности нет. Раньше она была. Обновление ядра у нас очень частое – примерно раз в неделю.

Что из этого следует? Пока сервер работал, время доступности составляло не больше недели на каждом из них, и "ngrep" спокойно выделял себе свою память. А через 17 дней он ее выделять перестал, поскольку ему нужно было выделить 31 непрерывный "неподкачиваемый" блок памяти по 32 мегабайта. Итого 92 мегабайта, которые на mmaps-сети рассчитанны, и которые нельзя "запихнуть" в подкачку. Не нашлось 31 блока.

Поэтому стоит вспомнить еще про один важный пункт. Когда вы проектируете приложение, у вас, как правило, сервер по разным причинам часто перезагружается. Это тестовый сервер, кому его жалко. На машине с временем доступности, допустим, в 15 дней, 1 месяц, 1 год будут возникать совершенно другие проблемы, связанные с фрагментированием памяти. С тем, что у вас какой-то процесс в памяти "висит" с июня, например. Он себе память выделяет настолько плохо, что уже потратил больше гигабайта.

В "Linux" это известная проблема. Чтобы mmap начал использовать "malloc" вместо "brk", и всем было счастье, еще определенные критерии должны выполниться. А если вы "malloc" делаете по пять байт, то у вас будет увеличиваться "brk", внутренняя фрагментация. Память процессора растет. Пройдет полгода, и этих 92 мегабайт непрерывно "неподкачанной" памяти у вас просто не будет на системе.

Следующая рекомендация. Если у вас продакшн-сервер будет иметь большое время доступности, ищите сервер с таким временем доступности. Попытайтесь "погоняться" на нем. Понятно, что тестироваться нужно на машине, идеально приближенной к продакшну, в том числе в этом аспекте.

Еще вопросы есть? Видимо, нет. Все. Тогда спасибо за внимание. 

Комментарии

Нет ни одного комментария

Только пользователи могут оставлять комментарии

Возможно, вам будет интересно:

Денис Бирюков

Денис Бирюков

Ведущий инженер программист в компании "Каванга".

Доклад об особенностях хранения данных пользователей баннерной системы.

Александр Калугин

Александр Калугин

Project Director в компании Mercury Development Russia. Докладчик профильных конференций SEF.BY, ReqLabs, SECR.

Рассказ о практическом опыте создания двух отдаленных офисов и распределенной команды на их основе.

Александр Погребняк

Александр Погребняк

Технический директор Alawar Entertainment.

Доклад про управление IT через сервисную модель.