|
|
Кстати, о SYN-flood :-)
Для начала скажу, что защититься от "флуда", если таковой достаточно мощный (заметно толще вашего собственного канала),
идет со "случайных" адресов и "похож" на настоящий трафик (grc.com повезло, что их DDoS'или
легко фильтруемым udp и icmp трафиком - а если бы в самом деле syn-flood?) -
защититься от него нельзя. Против лома нет приема, кроме другого лома, большего размером,
а против взвода стройбата с ломами у каждого - ломов не напасешься. Однако кое от чего
защититься можно.
Если физически "забит" внешний (до файрволла) канал - то тут уже не сделаешь ничего, в принципе.
Если канал не забит - можно попытаться "отфильтровать" трафик по каким-нибудь признакам.
Закрыть ненужные протоколы, запретить пакеты от несуществующих соединений, и тому подобное.
Веселее получается в случае SYN-flood'а, которым можно (было?) не напрягаясь "покласть"
небольшой сервер с модема, не отвлекаясь на поиски толстых каналов.
Рассмотрим типичную реализацию взаимодействия клиента, сервера (ОС), сервиса (программы,
обрабатывающей запрос клиента) в рамках tcp/ip. Фактическим стандартом
на реализацию сокетного API являются "Berkley sockets" с теми или иными вариациями,
и программы, естественно, пишутся используя то, что есть. Ну, а сам tcp/ip - он стандартен и един, иначе ничего бы и не работало.
Итак, взаимодействие. Клиент устанавливает tcp-соединение с сервером, делает "запрос",
сервис обрабатывает запрос и дает ответ.
Установка tcp-соединения идет в три этапа. Клиент посылает серверу пакет "хочу" - tcp syn.
Сервер посылает в ответ tcp syn-ack ("хочешь - бери") и ждет ответа tcp ack ("понял, не дурак").
С этого момента соединение считается установленным, и по нему можно гонять данные.
Сервис, желающий принимать соединения на каком-нибудь порту, зовет функцию listen()
и начинает "слушать" этот порт. По приходу SYN на этот порт, сервису тем или иным методом
("тушкой, чучелом", select(), WSA_*(), событие, сигнал, еще как) сообщают, что "клиент пришел".
Сервис, в соответствии со своими понятиями о загруженности, решает - принять или отбить,
и в случае если можно "принять" - вызывает на этот сокет accept(). Сервис получает
"сокет" - сущность, через которую он, сервис, будет коммуницировать :-) с клиентом,
и предпринимает сообразные этому действия - заводит структурку, ответственную за
работу с клиентом, форкается, создает нить, или передает данные одному из pre-forked
процессов, или просто опрашивает этот сокет в главном цикле обработки... в любом случае -
расходует драгоценные ресурсы, поскольку
для сервиса соединение уже состоялось, таблицы заполнены, ждем только запроса клиента.
...после вызова accept(), система посылает syn-ack и ждет ack. Ждет до таймаута,
который может измеряться минутами, и только после этого "ресетит" сокет. Всё это время
в сервисе "висит" полуоткрытый сокет, в позе "не умер - и не хоронят".
Поскольку syn-пакет маленький, сервисы не так уж часто позволяют иметь больше нескольких тысяч
одновременных соединений, а таймаут измеряется минутами - можно ввести сервер в состояние
кататонии с обычного модема, не утруждая себя поиском толстого канала или еще чего-нибудь такого.
Посмотрим, можно ли от этого защититься. В принципе - можно! Не защититься правда, а так, "смягчить удар",
но можно. Пусть есть файрволл. Приходящие syn-пакеты он (файрволл) "не пущает",
сам отсылает syn-ack, и сохраняет в табличке чего и куда он послал. В случае получения ack
на свой syn-ack, файрволл посылает серверу syn, и далее транслирует tcp-сессию между клиентом
и сервером через себя. В результате "поддельные" syn-пакеты до сервера не доходят вообще,
а на каждый syn расходуется десяток байт памяти файрволла - "когда, куда, откуда, чего хотел, чего ответили".
От классического син-флуда, который можно делать хоть с модема, это спасает. От более мощного
- уже нет, поскольку память-то все-таки расходуется, хотя и менее катастрофически и на файрволле
а не на сервере.
Короче, всё плохо, и скоро будет ещё хуже. И все мы беззащитны перед интернетом. Аминь :-)
| |
| |