В предыдущем материале я показывал базовый вариант ранней фильтрации входящего трафика на MikroTik через Spamhaus ZEN. Подход оказался рабочим - нагрузка на почтовые сервисы снизилась на 30-70%, а количество SMTP-сессий уменьшилось в несколько раз.
Но в процессе эксплуатации всплыли важные недостатки:
Единая точка отказа - если DNS Spamhaus недоступен, фильтрация полностью прекращается, и новые вредоносные IP перестают блокироваться до восстановления сервиса.
Ложное ощущение защиты - NXDOMAIN воспринимался скриптом как ошибка, из-за чего IP оставался в очереди навсегда.
Нет понимания причин блокировки - невозможно определить, какой DNSBL и по какой причине сработал.
Эти проблемы подтолкнули к переработке скрипта.
Нужно было добиться:
отказоустойчивости - если один DNSBL недоступен, автоматически переходить к следующему
прозрачности - каждый заблокированный IP должен иметь комментарий с указанием источника и причины
корректной очистки - IP должен удаляться из очереди при любом определённом ответе
устойчивости к race condition - скрипт не должен падать при параллельных запусках
Выбор fallback-зон
Помимо Spamhaus, я добавил несколько бесплатных DNSBL, работающих по тому же принципу (reversed IP + A-запись):
zen.spamhaus.org - основной источник (спам, вредоносное ПО, фишинг, ботнеты)
b.barracudacentral.org - Barracuda BRBL (репутационный список спамеров)
psbl.surriel.com - PSBL (пассивная фиксация спам-активности)
dnsbl.dronebl.org - DroneBL (ботнеты, прокси, заражённые устройства)
Приоритет задаётся порядком в массиве: сначала Spamhaus, затем остальные.
Критерии отбора
Бесплатность - все зоны доступны без регистрации
Единый DNS-механизм - запрос вида
{reversed IP}.{zone} → 127.0.0.xАктуальность - списки поддерживаются и обновляются
Релевантность - покрывают нужные типы угроз
Изменения в логике скрипта
Как было
Скрипт использовал только один DNSBL и не различал:
NXDOMAIN - IP чист
DNS timeout - сервер не ответил
Обе ситуации попадали в on-error{}, поэтому «чистые» IP оставались в очереди навсегда.
Как стало
Теперь используется цепочка зон:
dnszones = [spamhaus → barracuda → psbl → dronebl]
Для каждого IP:
для каждой зоны (пока не заблокирован):
─ resolve(fqdn)
├─ 127.0.0.x → определить категорию, блокировать, blocked=true
└─ NXDOMAIN → checked=true, перейти к следующей зоне
если blocked || checked:
─ удалить IP из spamhaus_check
иначе:
─ оставить для повтора (все DNSBL молчат)
Для каждого IP:
для каждой зоны:
если ответ 127.0.0.x → блокируем, фиксируем категорию
если NXDOMAIN → считаем IP проверенным и переходим к следующей зоне
если IP либо заблокирован, либо хотя бы одна зона дала осмысленный ответ → удаляем из очереди
если все зоны молчат (таймауты) → оставляем IP до следующего запуска
Ключевое нововведение - разделение состояний:
blocked=true - IP найден в списке
checked=true - хотя бы одна зона ответила NXDOMAIN
Если ни blocked, ни checked не выставлены - значит, все DNSBL недоступны.
Категоризация по источнику
Теперь комментарий в списке блокировки содержит источник (Spamhaus, Barracuda, PSBL, DroneBL) и тип угрозы (spam, malware, phishing, proxy, botnet и т.д.).
Это позволяет сразу понимать, почему IP заблокирован.
Spamhaus ZEN:
127.0.0.2- spam127.0.0.3- malware127.0.0.4- phishing127.0.0.5- spam+phishing127.0.0.6- spam+malware127.0.0.7- malware+phishing127.0.0.9- spam+malware+phishing
Barracuda BRBL:
127.0.0.2- spam (Barracuda)
DroneBL:
127.0.0.3- IRC Drone127.0.0.5- Bottler127.0.0.6- spambot/drone127.0.0.7- DDOS Drone127.0.0.8- SOCKS Proxy127.0.0.9- HTTP Proxy127.0.0.15- compromised router127.0.0.17- botnet (DroneBL)
Пример записи в address-list:
/ip firewall address-list add list=spamhaus_block address=185.xxx.xxx.xxx timeout=30d comment="spam (Barracuda)"Расширение фильтра приватных диапазонов
Добавлены исключения для:
100.64.0.0/10 - CGNAT
0.x.x.x - нулевые адреса
Эти диапазоны не должны попадать в очередь.
Устойчивость к race condition
В первой версии при параллельных запусках возникала ошибка:
no such item (4) (/ip/firewall/address-list/remove; line 35)Все операции удаления теперь обёрнуты в :do { remove } on-error={ }, что игнорирует попытку повторного удаления уже обработанного элемента.
Финальный код скрипта
:local dnszones {
"zen.spamhaus.org";
"b.barracudacentral.org";
"psbl.surriel.com";
"dnsbl.dronebl.org"
}
:local blockTimeout "30d"
:foreach i in=[/ip firewall address-list find list=spamhaus_check] do={
:local srcIP [/ip firewall address-list get $i address]
:if ([:len $srcIP] > 15 || [:find $srcIP "/"] >= 0) do={
:do { /ip firewall address-list remove $i } on-error={ }
:continue
}
:local p1 [:find $srcIP "."]
:local p2 [:find $srcIP "." ($p1 + 1)]
:local p3 [:find $srcIP "." ($p2 + 1)]
:local o1 [:pick $srcIP 0 $p1]
:local o2 [:pick $srcIP ($p1 + 1) $p2]
:local o3 [:pick $srcIP ($p2 + 1) $p3]
:local o4 [:pick $srcIP ($p3 + 1) [:len $srcIP]]
:if ($o1 = "10" || \
($o1 = "127") || \
($o1 = "169" && $o2 = "254") || \
($o1 = "172" && (($o2 >= 16 && $o2 <= 31))) || \
($o1 = "192" && $o2 = "168") || \
($o1 = "100" && (($o2 >= 64 && $o2 <= 127))) || \
[:tonum $o1] < 1) do={
:do { /ip firewall address-list remove $i } on-error={ }
:continue
}
:local revIP ($o4.".".$o3.".".$o2.".".$o1)
:local blocked false
:local checked false
:foreach zone in=$dnszones do={
:if (!$blocked) do={
:local fqdn ($revIP . "." . $zone)
:do {
:local res [:resolve $fqdn]
:local respStr [:tostr $res]
:if ([:pick $respStr 0 8] = "127.0.0.") do={
:local classCode [:pick $respStr 8 [:len $respStr]]
:local comment ""
:if ($zone = "zen.spamhaus.org") do={
:if ($classCode = "2") do={ :set comment "spam" }
:if ($classCode = "3") do={ :set comment "malware" }
:if ($classCode = "4") do={ :set comment "phishing" }
:if ($classCode = "5") do={ :set comment "spam+phishing" }
:if ($classCode = "6") do={ :set comment "spam+malware" }
:if ($classCode = "7") do={ :set comment "malware+phishing" }
:if ($classCode = "9") do={ :set comment "spam+malware+phishing" }
}
:if ($zone = "b.barracudacentral.org") do={
:if ($classCode = "2") do={ :set comment "spam (Barracuda)" }
}
:if ($zone = "psbl.surriel.com") do={
:set comment "listed (PSBL)"
}
:if ($zone = "dnsbl.dronebl.org") do={
:if ($classCode = "3") do={ :set comment "IRC Drone" }
:if ($classCode = "5") do={ :set comment "Bottler" }
:if ($classCode = "6") do={ :set comment "spambot/drone" }
:if ($classCode = "7") do={ :set comment "DDOS Drone" }
:if ($classCode = "8") do={ :set comment "SOCKS Proxy" }
:if ($classCode = "9") do={ :set comment "HTTP Proxy" }
:if ($classCode = "15") do={ :set comment "compromised router" }
:if ($classCode = "17") do={ :set comment "botnet (DroneBL)" }
}
:if ($comment = "") do={
:set comment ("blocked by " . $zone)
}
/ip firewall address-list add \
list=spamhaus_block address=$srcIP \
timeout=$blockTimeout comment=$comment
:log info ("Block " . $srcIP . " (" . $comment . ") by " . $zone)
:set blocked true
}
:set checked true
} on-error={
:set checked true
}
}
}
:if ($blocked || $checked) do={
:do { /ip firewall address-list remove $i } on-error={ }
}
}Что ещё изменилось
Таймаут блокировки увеличен с 1 дня до 30 дней
Проверка префикса стала явной, без регулярных выражений
Архитектура взаимодействия (RAW → Filter → Scheduler) осталась прежней
Ограничения
PSBL не даёт детальных кодов
Barracuda может ограничивать частоту запросов без регистрации
DroneBL ориентирован на ботнеты, а не на спам
Результат
После внедрения:
отказоустойчивость выросла - при падении Spamhaus работают остальные зоны
прозрачность улучшилась - комментарии показывают источник и тип угрозы
стабильность повысилась - ошибок в планировщике больше нет
очередь очищается корректно - «чистые» IP не висят бесконечно
Заключение
Переход от одного DNSBL к цепочке fallback-зон - логичный шаг для продакшена.
Spamhaus остаётся лучшим источником, но полагаться только на него рискованно.
Добавление Barracuda, PSBL и DroneBL:
не усложнило конфигурацию
значительно повысило надёжность фильтрации
Следующие шаги:
геолокационная фильтрация
сбор статистики по зонам
репутационное взвешивание
DNSBL-фильтрация - это не магия, а работа с данными. Чем больше независимых источников Вы используете - тем стабильнее результат.
Спасибо, что дочитали до конца. DNSBL-фильтрация --- это не магия, а методичная работа с данными. И чем больше независимых источников данных Вы используете, тем стабильнее результат. 😉

Станьте первым, кто прокомментирует эту запись