Поделюсь своим опытом борьбы с DDoS флудом. Защищать будем операционную систему openSUSE Linux с помощью правил iptables.
Для защиты настроим систему и создадим скрипт, который будет отслеживать флуд соединениями на открытые порты.
Сначала о настройке системы. Моё содержимое файла /etc/sysctl.conf:
# IPv6 здесь отключен, пинг отключен
kernel.sysrq = 0
net.ipv4.ip_forward = 0 #это если компьютер не используется как шлюз
net.ipv4.tcp_syncookies = 1
net.ipv6.conf.all.forwarding = 0
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1 #игнорируем broadcasts пакеты
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.conf.all.send_redirects = 0 #это если компьютер не используется как маршрутизатор
kernel.msgmnb = 65536
kernel.msgmax = 65536
kernel.shmmax = 4294967295
kernel.shmall = 268435456
net.ipv4.tcp_keepalive_time = 60
net.ipv4.tcp_keepalive_intvl = 10
net.ipv4.icmp_echo_ignore_all=1 #icmp полностью игнорируется
net.ipv4.tcp_max_syn_backlog=2048
net.ipv4.tcp_synack_retries=1
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_fin_timeout = 20
net.ipv4.tcp_window_scaling = 0
net.ipv4.tcp_sack = 0net.ipv4.tcp_timestamps = 0Теперь о правилах iptables.
Создайте файл /etc/init.d/iptables_myrules и сделайте его исполняемым: chmod +x /etc/init.d/iptables_myrules. Его содержимое:
#!/bin/bash
# PhazaSoft iptables rules
# Специально для пользователей SteelLinux
# http://steellinux.do.am
### BEGIN INIT INFO
# Provides: iptables_myrules
# Required-Start: $network
# Should-Start: $network
# Required-Stop:
# Should-Stop:
# Default-Start: 3 5
# Default-Stop: 0 1 2 6
# Short-Description: iptables user's rules
# Description: iptables user's rules
### END INIT INFO
########### НАСТРОЙКИ ########### ###>>>
# названия интерфейсов, которые будем защищать (через пробел)
PROTECTZONE="dsl0"
# названия интерфейсов, где защита не нужна (через пробел)
FREEZONE="lo eth0"
# перечень портов в кавычках через запятые пробелов, которые нужно разрешить (отдельно TCP и UDP)
TCP_PORTS="80,411,1209"
UDP_PORTS=""
# перечень портов только для разрешённых диапазонов
TCP_PORTS_PRIV=""
UDP_PORTS_PRIV=""
# разрешённые диапазоны для портов в TCP_PORTS_PRIV и UDP_PORTS_PRIV (если они заданы)
PRIV_RANGES="
0.0.0.0-0.0.0.0
127.0.0.0-127.255.255.255
192.168.0.0-192.168.255.255
172.20.0.0-172.20.255.255
"
# активировать правило connlimit для одновременных активных соединений с одного айпи? Указывается число соединений или 0-выключено.
IS_CONNLIMIT="10"	# 0-выкл
CONNLIMIT_MASK="32"	# маска для подсетей адресов одновременных активных соединений (32 - каждый IP индивидуален)
# правило ограничения количества соединений за заданное время с помощью recent
IS_RECENT="1"		# 1-вкл, 0-выкл
RECENT_SECONDS="60"	# период, за который не должно быть превышения количества соединений (в секундах)
RECENT_HITCOUNT="12"	# количество соединений за заданный период времени
# настройка ограничений количества соединений с помощью hashlimit
HASHLIMIT_UPTO="12/min"	# количество соединений в единицу времени
HASHLIMIT_BURST="6"	# пик количества разовой доставки соединений
HASHLIMIT_MODE="srcip"	# режим хеширования
HASHLIMIT_EXPIRE="60000"	# время жизни записи в хэш-таблице (в миллисекундах)
# разрешить GRE протокол (например, для VPN)
IS_GRE="1"		# 0-выкл
# udp broadcast трафик (в том чиcле IP-TV и прочее)
IS_BROADCAST="0"		# 1-вкл, 0-выкл
# icmp echo пакеты (пинги)
IS_ICMP_ECHO="0"		# 1-вкл, 0-выкл
# чёрный список IP-адресов, которые будут блокироваться
BLACKLIST_IP="
89.222.164.212
213.88.49.71
"
# чёрный список диапазонов IP-адресов, которые будут блокироваться
BLACKLIST_RANGES="
72.36.64.0-72.36.127.255
130.126.0.0-130.126.255.255
192.17.0.0-192.17.255.255
"
# чёрный список кодов стран, которые будут блокироваться (модуль geoip)
BLACKLIST_COUNTRIES="CN,KR,JP" # через запятую без пробелов
IPT=/usr/sbin/iptables	# путь к iptables
################################# ###<<<
_VERSION="1.1"
#преобразуем список портов в секции, не превышающие 15 портов на секцию (ограничение в multiport)
TCP_PORTS=$(echo "$TCP_PORTS" | sed -E 's/\s//g')
TCP_PORTS_PARSED=""
while [ $(echo $TCP_PORTS | sed -E 's/,/ /g' | wc -w) -gt "15" ]
do
  TCP_PORTS_PARSED="$TCP_PORTS_PARSED $(echo $TCP_PORTS | sed -E 's/([0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*),.*/\1/')"
  TCP_PORTS=$(echo $TCP_PORTS | sed -E 's/[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,(.*)/\1/')
done
TCP_PORTS_PARSED="$TCP_PORTS_PARSED $TCP_PORTS"
UDP_PORTS=$(echo "$UDP_PORTS" | sed -E 's/\s//g')
UDP_PORTS_PARSED=""
while [ $(echo $UDP_PORTS | sed -E 's/,/ /g' | wc -w) -gt "15" ]
do
  UDP_PORTS_PARSED="$UDP_PORTS_PARSED $(echo $UDP_PORTS | sed -E 's/([0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*),.*/\1/')"
  UDP_PORTS=$(echo $UDP_PORTS | sed -E 's/[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,(.*)/\1/')
done
UDP_PORTS_PARSED="$UDP_PORTS_PARSED $UDP_PORTS"
TCP_PORTS_PRIV=$(echo "$TCP_PORTS_PRIV" | sed -E 's/\s//g')
TCP_PORTS_PRIV_PARSED=""
while [ $(echo $TCP_PORTS_PRIV | sed -E 's/,/ /g' | wc -w) -gt "15" ]
do
  TCP_PORTS_PRIV_PARSED="$TCP_PORTS_PRIV_PARSED $(echo $TCP_PORTS_PRIV | sed -E 's/([0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*),.*/\1/')"
  TCP_PORTS_PRIV=$(echo $TCP_PORTS_PRIV | sed -E 's/[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,(.*)/\1/')
done
TCP_PORTS_PRIV_PARSED="$TCP_PORTS_PRIV_PARSED $TCP_PORTS_PRIV"
UDP_PORTS_PRIV=$(echo "$UDP_PORTS_PRIV" | sed -E 's/\s//g')
UDP_PORTS_PRIV_PARSED=""
while [ $(echo $UDP_PORTS_PRIV | sed -E 's/,/ /g' | wc -w) -gt "15" ]
do
  UDP_PORTS_PRIV_PARSED="$UDP_PORTS_PRIV_PARSED $(echo $UDP_PORTS_PRIV | sed -E 's/([0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*),.*/\1/')"
  UDP_PORTS_PRIV=$(echo $UDP_PORTS_PRIV | sed -E 's/[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,(.*)/\1/')
done
UDP_PORTS_PRIV_PARSED="$UDP_PORTS_PRIV_PARSED $UDP_PORTS_PRIV"
do_rules() {
  echo 1 > /proc/sys/net/ipv4/ip_forward
  echo 0 > /proc/sys/net/ipv4/ip_forward   #если наш компьютер не используется как шлюз
  for i in /proc/sys/net/ipv4/conf/*/send_redirects; do echo 0 > $i; done   #если наш компьютер не используется как маршрутизатор
  for i in /proc/sys/net/ipv4/conf/*/rp_filter; do echo 1 > $i; done
  echo 1 > /proc/sys/net/ipv4/tcp_syncookies
  echo 1 > /proc/sys/net/ipv4/icmp_ignore_bogus_error_responses
  for i in /proc/sys/net/ipv4/conf/*/accept_redirects; do echo 0 > $i; done
  for i in /proc/sys/net/ipv4/conf/*/accept_source_route; do echo 0 > $i; done
  for i in /proc/sys/net/ipv4/conf/*/mc_forwarding; do echo 0 > $i; done
  for i in /proc/sys/net/ipv4/conf/*/proxy_arp; do echo 0 > $i; done
  for i in /proc/sys/net/ipv4/conf/*/secure_redirects; do echo 1 > $i; done
  for i in /proc/sys/net/ipv4/conf/*/bootp_relay; do echo 0 > $i; done
  
  # Настраиваем политики по умолчанию
  $IPT -P INPUT DROP   #политика по умолчанию для входящих - запрет
  $IPT -P OUTPUT ACCEPT
  $IPT -P FORWARD ACCEPT
  # удаляем все имеющиеся правила
  $IPT -F
  $IPT -t nat -F
  $IPT -t mangle -F
  $IPT -X
  $IPT -t nat -X
  $IPT -t mangle -X
  
  $IPT -Z
  $IPT -t nat -Z
  $IPT -t mangle -Z
  
  for interface in $FREEZONE
  do
    $IPT -A INPUT -i $interface -j ACCEPT
    $IPT -A OUTPUT -o $interface -j ACCEPT
  done #разрешаем активность на незащищаемых интерфейсах
  
  $IPT -A INPUT -m conntrack --ctstate INVALID -j DROP
  $IPT -A OUTPUT -m conntrack --ctstate INVALID -j DROP
  $IPT -A FORWARD -m conntrack --ctstate INVALID -j DROP
  
  $IPT -A INPUT -p tcp -m conntrack --ctstate NEW --tcp-flags ALL ALL -j DROP
  $IPT -A INPUT -p tcp -m conntrack --ctstate NEW --tcp-flags ALL NONE -j DROP
  $IPT -A INPUT -p tcp -m conntrack --ctstate NEW ! --syn -j DROP
  $IPT -A INPUT -m conntrack --ctstate NEW,INVALID -p tcp --tcp-flags SYN,ACK SYN,ACK -j REJECT --reject-with tcp-reset
  
  $IPT -A INPUT -p tcp --tcp-flags ALL FIN,URG,PSH -j DROP
  $IPT -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j DROP
  $IPT -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP
  
  #блокируем некоторые айпи и страны
  for ip in $BLACKLIST_IP
  do
    $IPT -I INPUT -s $ip -j DROP
  done
  for range in $BLACKLIST_RANGES
  do
    $IPT -I INPUT -m iprange --src-range $range -j DROP
  done
  for countries in $BLACKLIST_COUNTRIES
  do
    $IPT -I INPUT -m geoip --src-cc $countries -j DROP
  done
  
  
  if [ $IS_RECENT -gt "0" ]
  then
    for ports in $TCP_PORTS_PARSED
    do
      $IPT -A INPUT -p tcp -m tcp -m multiport --dports $ports -m conntrack --ctstate NEW -m recent --set --name ddos_block_conn_tcp
      $IPT -A INPUT -p tcp -m tcp -m multiport --dports $ports -m conntrack --ctstate NEW -m recent --update --seconds $RECENT_SECONDS --hitcount $RECENT_HITCOUNT --name ddos_block_conn_tcp -j DROP
    done
    
    for ports in $UDP_PORTS_PARSED
    do
      $IPT -A INPUT -p udp -m udp -m multiport --dports $ports -m conntrack --ctstate NEW -m recent --set --name ddos_block_conn_udp
      $IPT -A INPUT -p udp -m udp -m multiport --dports $ports -m conntrack --ctstate NEW -m recent --update --seconds $RECENT_SECONDS --hitcount $RECENT_HITCOUNT --name ddos_block_conn_udp -j DROP
    done
    
    for range in $PRIV_RANGES
    do
      for ports in $TCP_PORTS_PRIV_PARSED
      do
        $IPT -A INPUT -p tcp -m tcp -m multiport --dports $ports -m iprange --src-range $range -m conntrack --ctstate NEW -m recent --set --name ddos_block_conn_tcp
        $IPT -A INPUT -p tcp -m tcp -m multiport --dports $ports -m iprange --src-range $range -m conntrack --ctstate NEW -m recent --update --seconds $RECENT_SECONDS --hitcount $RECENT_HITCOUNT --name ddos_block_conn_tcp -j DROP
      done
      
      for ports in $UDP_PORTS_PRIV_PARSED
      do
        $IPT -A INPUT -p udp -m udp -m multiport --dports $ports -m iprange --src-range $range -m conntrack --ctstate NEW -m recent --set --name ddos_block_conn_udp
        $IPT -A INPUT -p udp -m udp -m multiport --dports $ports -m iprange --src-range $range -m conntrack --ctstate NEW -m recent --update --seconds $RECENT_SECONDS --hitcount $RECENT_HITCOUNT --name ddos_block_conn_udp -j DROP
      done
    done
  fi #ограничение количества соединений за заданное время
  
  for interface in $PROTECTZONE
  do
    for ports in $TCP_PORTS_PARSED
    do
      if [ $IS_CONNLIMIT -gt "0" ]
      then
        $IPT -A INPUT -i $interface -p tcp -m multiport --dports $ports -m connlimit --connlimit-above $IS_CONNLIMIT --connlimit-mask $CONNLIMIT_MASK -j DROP
      fi
      $IPT -A INPUT -i $interface -p tcp -m multiport --dports $ports -m conntrack --ctstate NEW -m hashlimit --hashlimit-upto $HASHLIMIT_UPTO --hashlimit-burst $HASHLIMIT_BURST --hashlimit-mode $HASHLIMIT_MODE --hashlimit-htable-expire $HASHLIMIT_EXPIRE --hashlimit-name ddos_block_tcp -j ACCEPT
    done
    
    for ports in $UDP_PORTS_PARSED
    do
      if [ $IS_CONNLIMIT -gt "0" ]
      then
        $IPT -A INPUT -i $interface -p udp -m multiport --dports $ports -m connlimit --connlimit-above $IS_CONNLIMIT --connlimit-mask $CONNLIMIT_MASK -j DROP
      fi
      $IPT -A INPUT -i $interface -p udp -m multiport --dports $ports -m conntrack --ctstate NEW -m hashlimit --hashlimit-upto $HASHLIMIT_UPTO --hashlimit-burst $HASHLIMIT_BURST --hashlimit-mode $HASHLIMIT_MODE --hashlimit-htable-expire $HASHLIMIT_EXPIRE --hashlimit-name ddos_block_udp -j ACCEPT
    done
    
    for range in $PRIV_RANGES
    do
      for ports in $TCP_PORTS_PRIV_PARSED
      do
        if [ $IS_CONNLIMIT -gt "0" ]
        then
          $IPT -A INPUT -i $interface -p tcp -m multiport --dports $ports -m iprange --src-range $range -m connlimit --connlimit-above $IS_CONNLIMIT --connlimit-mask $CONNLIMIT_MASK -j DROP
        fi
        $IPT -A INPUT -i $interface -p tcp -m multiport --dports $ports -m iprange --src-range $range -m conntrack --ctstate NEW -m hashlimit --hashlimit-upto $HASHLIMIT_UPTO --hashlimit-burst $HASHLIMIT_BURST --hashlimit-mode $HASHLIMIT_MODE --hashlimit-htable-expire $HASHLIMIT_EXPIRE --hashlimit-name ddos_block_tcp -j ACCEPT
      done
      
      for ports in $UDP_PORTS_PRIV_PARSED
      do
        if [ $IS_CONNLIMIT -gt "0" ]
        then
          $IPT -A INPUT -i $interface -p udp -m multiport --dports $ports -m iprange --src-range $range -m connlimit --connlimit-above $IS_CONNLIMIT --connlimit-mask $CONNLIMIT_MASK -j DROP
        fi
        $IPT -A INPUT -i $interface -p udp -m multiport --dports $ports -m iprange --src-range $range -m conntrack --ctstate NEW -m hashlimit --hashlimit-upto $HASHLIMIT_UPTO --hashlimit-burst $HASHLIMIT_BURST --hashlimit-mode $HASHLIMIT_MODE --hashlimit-htable-expire $HASHLIMIT_EXPIRE --hashlimit-name ddos_block_udp -j ACCEPT
      done
    done
  done # открываем входящие порты на защищаемых интерфейсах
  
  if [ $IS_BROADCAST -eq "0" ]
  then
    echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts
    $IPT -A INPUT -p udp -m pkttype --pkt-type broadcast -j DROP
  else
    echo 0 > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts
  fi #запрещаем broadcast пакеты
  
  if [ $IS_ICMP_ECHO -eq "0" ]
  then
    echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
    #$IPT -A INPUT -p icmp --icmp-type echo-request -j DROP
    #$IPT -A INPUT -p icmp -j DROP
    #$IPT -A OUTPUT -p icmp -j ACCEPT
  else
    echo 0 > /proc/sys/net/ipv4/icmp_echo_ignore_all
  fi # запрещаем пинги
  
  
  for interface in $PROTECTZONE
  do
    $IPT -A INPUT -i $interface -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
    $IPT -A OUTPUT -o $interface -m conntrack --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT
    if [ $IS_GRE -gt "0" ]
    then
      $IPT -A INPUT -i $interface -p gre -j ACCEPT
      $IPT -A OUTPUT -o $interface -p gre -j ACCEPT
    fi
  done #разрешаем входящую активность уже установленных соединений и создание исходящих соединений на защищаемых интерфейсах
}
case $1 in
'list')
  $IPT -L -n -v -x
  ;;
  
'clean')
  $IPT -P INPUT ACCEPT
  $IPT -P OUTPUT ACCEPT
  $IPT -P FORWARD ACCEPT
  $IPT -F
  $IPT -L -n -v -x
  ;;
  
'help')
  echo "PhazaSoft antiddos v.$_VERSION"
  echo 'iptables_myrules [start|clean|list|help]'
  ;;
  
*)
  do_rules
  ;;
esac
Здесь заданы непосредственно правила iptables.
Рассмотрим настройки:
PROTECTZONE - в этой константе перечисляются через пробел интерфейсы, которые будут защищаться правилами (внешняя зона). Обычно это один интерфейс, через который осуществляется выход в интернет. В данном примере это интерфейс dsl0. Вы должны задать свой интерфейс.
FREEZONE - здесь перечисляются через пробел интерфейсы внутренней зоны, на которой разрешена любая активность (локальные интерфейсы). Интерфейс lo должен обязательно быть здесь.
Если у Вас лишь один cетевой интерфейс (например, eth0), через который осуществляется выход в интернет, то он должен быть указан во внешней зоне, а интефейс lo во внутренней.
TCP_PORTS - перечень TCP портов через запятую без пробелов, которые нужно открыть (на которых у нас работают те или иные сервисы, принимающие входящие соединения из внешней зоны).
UDP_PORTS - перечень UDP портов через запятую без пробелов, которые нужно открыть.
TCP_PORTS_PRIV и UDP_PORTS_PRIV - аналогичны константам TCP_PORTS и UDP_PORTS соответственно, только к перечисленным здесь портам будут разрешены подключения лишь с определённых диапазонов IP-адресов, перечисленных в константе PRIV_RANGES (приватные диапазоны).
PRIV_RANGES - перечень приватных диапазонов IP-адресов (начальный-конечный, без пробелов через дефиз), по одному диапазону на каждой строчке. Перечисленные здесь адреса допускаются для соединения с портами, перечисленными в константах TCP_PORTS_PRIV и UDP_PORTS_PRIV. Чтобы задать лишь один IP-адрес, просто укажите одинковые начальный и конечный адреса диапазона.
IS_CONNLIMIT - задаётся разрешённое число оновременных соединений с одного IP-адреса (модуль connlimit). Если задать значение '0', то правило будет отключено.
CONNLIMIT_MASK - маска для проверки одновременных соединений модуля connlimit.
IS_RECENT - активация модуля recent, который ограничивает число соединений с одного адреса за определённый период времени. 1 - включено, 0 - выключено.
RECENT_SECONDS - период в секундах, за который не должно быть превышения лимита количества соединений за заданный интервал времени с одного IP-адреса.
RECENT_HITCOUNT - количество соединений за заданный период времени для модуля recent.
HASHLIMIT_UPTO - количество соединений в единицу времени для модуля hashlimit. Временной интервал может быть: /sec, /min, /hour, /day. При превышении данного лимита пакет будет заблокирован.
HASHLIMIT_BURST - пик количества разовой доставки соединений для модуля hashlimit.
HASHLIMIT_MODE - режим хеширования для модуля hashlimit. Варианты могут быть: dstip, srcip, dstport, srcport (несколько разделяютя запятыми без пробелов).
HASHLIMIT_EXPIRE - время жизни записи в хэш-таблице для модуля hashlimit.
IS_GRE - разрешение протокола GRE (1 - включено, 0 - выключено).
IS_BROADCAST - разрешение broadcast трафика (1 - включено, 0 - выключено).
IS_ICMP_ECHO - icmp echo пакеты (1 - включено, 0 - выключено).
BLACKLIST_IP - здесь можно указать перечень IP-адресов, которые будут блокироваться (по одному на каждой строчке).
BLACKLIST_RANGES - здесь можно указать перечень диапазонов IP-адресов, которые будут блокироваться (по одному на каждой строчке, через дефиз без пробелов).
BLACKLIST_COUNTRIES - перечень кодов стран, которые будут блокироваться, через запятую без пробелов (модуль geoip).
Если система используется как шлюз или маршрутизатор, то нужно разрешить ip_forward и send_redirects.
При запуске скрипта без параметров все правила перезадаются. При запуске с параметром clean правила обнуляются. Параметр list выводит текущий список правил и статистику срабатываний.
Для добавления скрипта в автозапуск, выполните команду: chkconfig -a iptables_myrules. Для удаления скрипта из автозапуска выполните команду: chkconfig iptables_myrules off.
Теперь опишу дополнительный скрипт ddos_block.lua, который следит за входящими соединениями на заданные порты (или группы портов) и блокирует IP-адреса, с которых превышен лимит одновременного числа установленных соединений. Этот лимит задаётся отдельно для каждого порта или группы портов. Вот код скрипта:
#!/usr/bin/lua
--[[
  PhazaSoft DDoS antiflood
  Специально для пользователей SteelLinux
  http://steellinux.do.am
]]
-- ################## НАСТРОЙКА ################## >>>
local ports_protect={
	[{22}]=2,
	[{80,413}]=15,
	[{411,1209}]=3,
} -- защищаемые порты (группа задаётся через запятую) и разрешённое количество одновременных соединений для данной группы портов с одного IP
local ranges_allow={
	[{"0.0.0.0","0.0.0.0"}]="all", --local
	[{"10.0.0.0","10.255.255.255"}]="all",
	[{"127.0.0.0","127.255.255.255"}]="all",
	[{"172.16.0.0","172.31.255.255"}]="all",
	[{"192.168.0.0","192.168.255.255"}]="all",
} -- разрешённые диапазоны (white list) и порты для них через запятую (all означает все порты)
local time_ban=60*30 --время блокировки IP-адреса (в секундах)
local drop_allow=200 --разрешённое количество попыток соединения во время блокировки (при превышении блокировка продлевается)
local scan_period=10 --период между сканированиями (в секундах)
local log_folder="/var/log/ddos_block/" --папка для логов (в конце должен быть "/") (пустая строка "" означает отключение логов)
local filter_command="/bin/netstat -utan | egrep '^(tcp|udp)\\s+[0-9]+\\s+[0-9]+\\s+[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+:(%PORTS)' " --шаблон команды для получения списка текущих соединений с защищаемыми портами (шаблон %PORTS заменится на защищаемые порты)
local ban_command="/usr/sbin/iptables -I INPUT -s %IP -j DROP " --шаблон команды блокировки IP-адреса
local unban_command="/usr/sbin/iptables -D INPUT -s %IP -j DROP " --шаблон команды удаления блокировки IP-адреса
local stat_command="/usr/sbin/iptables -L -n -v -x | grep 'DROP       all  \\-\\-  \\*      \\*' | grep -v 'all  \\-\\-  \\*      \\*       0.0.0.0/0' " --команда для получения статистики о заблокированных IP-адресах
-- ############################################## <<<
local VERSION="1.1"
local res,err=io.popen("whoami")
if res then
	local result=res:read("*a") or "" 
	res:close()
	if result:gsub("%s","")~="root" then
		print("ERR", "Run this program as root! Exiting.")
		return
	end
else
	print("ERR:popen:whoami", os.date(), tostring(err))
	return
end --нам нужны права рута
local function IPToLong(str)
	local d1,d2,d3,d4=str:match("(%d+)%.(%d+)%.(%d+)%.(%d+)")
	if not d1 or not d2 or not d3 or not d4 then return end
	d1=d1*1000000000
	d2=d2*1000000
	d3=d3*1000
	d4=d4+0
	if d1<=255000000000 and d2<=255000000 and d3<=255000 and d4<=255 then return d1+d2+d3+d4 end
end --преобразование строки айпи в число
local function LongToIP(num)
	local d1,_=math.modf(num/1000000000)
	num=num-(d1*1000000000)
	local d2,_=math.modf(num/1000000)
	num=num-(d2*1000000)
	local d3,_=math.modf(num/1000)
	local d4=num-(d3*1000)
	return d1.."."..d2.."."..d3.."."..d4
end --преобразование числа в строку айпи
local function FilterIPinRanges(tab)
	for ip in pairs(tab) do
		local lip=IPToLong(ip) or (print("ERR:IPToLong", os.date(), tostring(ip)))()
		for i in pairs(ranges_allow) do
			if lip>=i[1] and lip<=i[2] then
				if not next(ranges_allow[i]) then
					tab[ip]=nil
				else
					for p in pairs(tab[ip]) do
						if ranges_allow[i][p..""] then tab[ip][p]=nil end
					end
					if not next(tab[ip]) then tab[ip]=nil end
				end --разрешены все порты или некоторые?
				break
			end --ip в белом листе
		end
	end
	return tab
end --сброс статистики соединений с разешёнными портами для ip из белого листа
local ip_banned={} --заблокированные ip
local ip_stat={} --текущая статистика
res=""
for i in pairs(ports_protect) do
	for j=1,#i do res=res..i[j].." |" end
end --составляем команду фильтра защищаемых портов
res,_=res:gsub("|$","")
filter_command,_=filter_command:gsub("%%PORTS",res)
for i in pairs(ranges_allow) do
	res=ranges_allow[i]
	ranges_allow[i]={}
	for p in res:gmatch("(%d+)") do ranges_allow[i][p]=1 end
	i[1]=IPToLong(i[1])
	i[2]=IPToLong(i[2])
end --преобразование диапазонов ip в числа и портов в таблицы
if log_folder~="" then
	os.execute("mkdir -p "..log_folder.."banned")
	os.execute("mkdir -p "..log_folder.."unbanned")
end
print("Protected ports:")
res=""
for i in pairs(ports_protect) do
	for j=1,#i do res=res..i[j].."," end
	res,_=res:gsub(",$","")
	print("", res, "("..ports_protect[i].." conn. allow)")
	res=""
end
print("Ban time:", time_ban.." sec.")
print("Connection attempts count allowed while banned:", drop_allow)
print("Period between scans:", scan_period.." sec.")
print("Logs folder:", (log_folder=="" and "disabled" or log_folder))
print("START", os.date(), "v."..VERSION)
while true do
res,err=io.popen(stat_command)
if res then
	local result=res:read("*a") or ""
	res:close()
	local tab={}
	for c,ip in result:gmatch("(%d+)%s+%d+%s+DROP%s+all%s+%-%-%s+%*%s+%*%s+(%d+%.%d+%.%d+%.%d+)") do
		tab[ip]=c+0
	end
	for ip,c in pairs(tab) do
		if ip_banned[ip] then
			local c0,t,d=(ip_banned[ip]):match("^(%d+)%s+(%d+)%s+(.+)")
			if os.time()-t > time_ban then
				if (c+0<ip_stat[ip] and c+0<=drop_allow) or (c+0>=ip_stat[ip] and c-ip_stat[ip]<=drop_allow) then
					local s,_=unban_command:gsub("%%IP",ip)
					res,err=io.popen(s)
					if res then
						res:close()
						print("UNBAN", ip, c, os.date())
						if log_folder~="" then
							os.execute("mv -f "..log_folder.."banned/"..ip.." "..log_folder.."unbanned/ 2> /dev/null")
						end
						ip_banned[ip]=nil
						ip_stat[ip]=nil
					else
						print("ERR:popen:unban_command", os.date(), tostring(err))
					end --пробуем разблокировать
				else
					print("PROLONG", ip, c, os.date())
					if log_folder~="" then
						os.execute("echo '>>> "..os.date().."\t"..c.." (PROLONG)' >> "..log_folder.."banned/"..ip.." ; /bin/netstat -utanp | grep "..ip.." >> "..log_folder.."banned/"..ip)
					end
					ip_banned[ip]=c0.."\t"..os.time().."\t"..d
					ip_stat[ip]=c+0
				end --не превышает ли количество попыток соединения разрешённое число во время блокировки? Продлеваем бан или разблокируем?
			end --возможно, пришло время разблокировать?
		end --проверяем список блокировок на возможность разблокировки
	end --сканируем текущие соединения
	
	for ip in pairs(ip_banned) do
		if tab[ip]==nil then
			print("ZOMBIE", ip, "", os.date())
			if log_folder~="" then
				os.execute("mv -f "..log_folder.."banned/"..ip.." "..log_folder.."unbanned/ 2> /dev/null")
			end
			ip_banned[ip]=nil
			ip_stat[ip]=nil
		end
	end --удаление из базы банов-призраков
	tab=nil
else
	print("ERR:popen:stat_command", os.date(), tostring(err))
end --проверка статистики блокировок
res,err=io.popen(filter_command)
if res then
	local result=res:read("*a") or ""
	res:close()
	local tab={}
	for c,ip in result:gmatch("%w+%s+%d+%s+%d+%s+[%d%.]+:(%d+)%s+(%d+%.%d+%.%d+%.%d+)") do
		if not tab[ip] then tab[ip]={} end
		tab[ip][c]=(tab[ip][c] or 0)+1
	end --составление списка текущих соединений с сортировкой по портам
	tab=FilterIPinRanges(tab) --фильтруем белый список диапазонов ip
	for ip in pairs(tab) do
		for i in pairs(ports_protect) do
			local gres=0
			for j=1,#i do
				gres=gres+(tab[ip][i[j]..""] or 0)
			end
			if gres>ports_protect[i] then
				if ip_banned[ip]==nil then
					local s,_=ban_command:gsub("%%IP",ip)
					res,err=io.popen(s)
					if res then
						res:close()
						s,_=unban_command:gsub("%%IP",ip)
						print("BAN", ip, gres, os.date(), "(for unban: "..s..")")
						if log_folder~="" then
							os.execute("mv -f "..log_folder.."unbanned/"..ip.." "..log_folder.."banned/ 2> /dev/null ; echo '>>> "..os.date().."\t"..gres.."' >> "..log_folder.."banned/"..ip.." ; /bin/netstat -utanp | grep "..ip.." >> "..log_folder.."banned/"..ip)
						end
						ip_banned[ip]=gres.."\t"..os.time().."\t"..os.date()
						ip_stat[ip]=0
						--os.execute("sleep 1")
					else
						print("ERR:popen:ban_command", os.date(), tostring(err))
					end --пытаемся заблокировать
				else
					local c0,t,d=(ip_banned[ip]):match("^(%d+)%s+(%d+)%s+(.+)")
					ip_banned[ip]=(c0+0>gres) and (c0.."\t"..os.time().."\t"..d) or (gres.."\t"..os.time().."\t"..d) --просто обновляем данные об ip
				end --новый ip или уже заблокированный
				break
			end --блокируем ip, если превышен лимит одновременных соединений к группам защищаемых портов
		end
	end --вычисляем количетво соединений к защищаемым портам для всех ip
else
	print("ERR:popen:filter_command", os.date(), tostring(err))
end --проверяем текущие соединения
os.execute("sleep "..scan_period)
end
print("STOP", os.date())Для удобства запуска сделайте его исполняемым. Скрипт написан на языке LUA. Рассмотрим настройки:
ports_protect - перечень защищаемых портов и разрешённое количество соединений с одного IP для каждого из них (или их группы). Количество соединений к группе портов отслеживается в совокупности, как к одному. Формат таблицы таков, что на каждой строке должна быть запись вида:
[{P1,P2,...,Pn}]=N,
где P1,P2,...,Pn - перечень группы портов через запятую, соединения к которым отслеживаются в совокупности. Здесь может быть просто один порт. N - количество разрешённых одновременно установленных соединений к данному порту или группе.
ranges_allow - перечень диапазонов IP-адресов и соответствующий им список портов. Перечисленные здесь адреса не будут ограничены по количеству соединений на указанные порты. Формат таблицы таков, что на каждой строке должна быть запись вида:
[{'IPstart','IPend'}]='P1,P2,...,Pn',
где IPstart - начальный адрес диапазона, IPend - конечный адрес диапазона, P1,P2,...,Pn - перечень разрешённых для данного диапазона портов, соединения к которым не будет ограничиватья. Если в качестве переченя портов указано 'all', то будут разрешены все порты. Чтобы задать только один IP-адрес, просто укажите его как начальный и конечный адрес диапазона.
time_ban - время блокировки IP-адреса, который превысил разрешённый лимит соединений (в секундах).
drop_allow - разрешённое количество попыток соединения во время блокировки. Если после истечения времени блокировки количество попыток соединения не будет превышать разрешённое число, IP-адрес будет разблокирован. В противном случае, блокировка будет продлена ещё на один срок.
scan_period - период между сканированиями в секундах.
log_folder - полный путь к папке для записи логов блокировок и разблокировок (должен оканчиваться слэшем). Если путь не задан (пустая строка), то ведение логов будет отключено.
Константы ниже менять не рекомендуется.
filter_command - шаблон команды для получения списка текущих соединений с защищаемыми портами (шаблон %PORTS заменится на защищаемые порты).
ban_command - шаблон команды блокировки IP-адреса.
unban_command - шаблон команды удаления блокировки IP-адреса.
stat_command - команда для получения статистики о заблокированных IP-адресах.
Данный скрипт должен быть постоянно запущен в терминале.
Вышеописанные методы отлично помогали против DDoS-атак даже на слабом ADSL-соединении, линк не терялся, серверы продолжали работу. Количество ботов было порядка 2000. У себя на ресурсах я использую свою же сборку SteelLinux на базе openSUSE. Во всяком случае, описанные ниже методы работоспособны на этой системе (на Ubuntu, например, это уже не помогало и сервер на её базе падал в оффлайн).
Скачать скрипты можно тут:
iptables_myrules
ddos_block.lua





