[Из песочницы] Отстрел чужих DHCP-серверов на коммутаторе MikroTik CRS
Распространенные в сети материалы касаются работы именно маршрутизаторов MikroTik. И все они для отлова DHCP используют скудные ресурсы CPU. При этом, относительно свежая линейка коммутаторов хронически остается без внимания. Между прочим, совершенно напрасно.
Итак, подопытный аппарат CRS125–24G-1S установлен на доступе. С некоторых пор изредка на пользовательские устройства стали выпадать посторонние IP-адреса. Естественно возникла задача «найти и обезвредить». В стандартном арсенале RouterOS есть инструмент «DHCP-server Alert», способный вызывать некие действия при обнаружении в сети стороннего DHCP-сервера.
Отлично? Пожалуй. Только инструмент при ближайшем рассмотрении оказался не слишком информативный. Чтобы понять почему, рассмотрим типичную архитектуру маршрутизирующего оборудования MikroTik (картинка из вики):
По сути основная часть устройства делится на две:
1) програмно-конфигурируемый свитч, осуществляющий коммутацию на максимальной скорости;
2) CPU отвечающий за маршрутизацию, фильтрацию, а также умеющий осуществлять коммутацию на программном уровне, медленнее и более ресурсоёмко.
IP-сервисы на таком устройстве, само собой висят на верхнем уровне. На Master-интерфейсе восходящем к CPU Routerboard или на программном бридже. Там и живет DHCP-сервер и его служба алертов. Все виденные мною ранее решения имели также два недостатка вытекающие из этой архитектуры:
1) Фильтрация трафика от злодея выполнялась на бридже, через «use ip firewall» и отнимала без того скудные ресурсы CPU;
2) Злодей подключенный к свитчу продолжал неконтролируемо пакостить абонентам на соседних портах этого свича.
Например, абоненты висящие на port2 и port3 всё равно продолжали получать бяку прилетающую со стороны port4. Но всё изменилось, когда у MikroTik появилась линейка CRS — ребята установили более мощный и функциональный свитч-чип, который я и решил попытаться использовать, победить и сохранить ресурсы CPU.
Шаг №1
Настраиваем «DHCP-server Alert». Настройка сама по себе не сложная, почти всё описано в вендорской вики — включаем, задаем интерфейс на котором следить (например bridge-local), указываем доверенный наш DHCP. Можем добавить скрипт, который вызывать в случае чего. Ура — скрипту автоматически передаются параметры: $interface $mac-address $address обнаруженного злодея! Ура? Так легко? Но вот тут-то и спрятаны первые подводные грабли. И не одни.
Грабли первые: в $interface передается имя интерфейса на котором сидит сам алертер. Потому, если даже злодей подключен к интерфейсу «port4» (см. картинку выше), в переменной всё равно будет «bridge-local». В логе видим почти то же самое:
"dhcp-alert on bridge-local: discovered unknown dhcp server, mac xx:xx:xx:xx:xx:xx, ip xyz.xyz.xyz.xyz"
Кхм. Сколько у нас портов? 24? Ладно. Я иду искать…
Шаг №2
По граблям. Искать приходится в unicast-fdb свича, по переменной $mac-address. И вот тут-то меня ждали вторые грабли. Настоящие подводные. Скрипт вызывался, но никак не хотел работать. Кто занимается скриптописательством для MikroTik подтвердит — отладка автоматически запускаемого скрипта сама по себе задача нифига не тривиальная. Да еще и обработчик on-error{} почему-то молчал. Опытным путём было обнаружено, что код
:set portname [/interface ethernet switch unicast-fdb get [find mac-address=$mac-address] port ]
отлично отрабатывает в командной строке терминала и только в ней. В теле скрипта не желает. Причина — странная интерпретация имени собственной (определенной вендором!) переменной $mac-address.
Фича: интерпретация команд в скрипте идет через… иным путём. Для успешной работы, имя системной переменной надо экранировать: ($"mac-address")
. При этом в дальнейшем использовании тоже не исключены фокусы.
Я пошел другим путем, создав локальную переменную с нормально интерпретируемым именем:
:local smac ($"mac-address")
Стало легче. Появилась возможность видеть название реального интерфейса на свитче, к которому подключен вредитель. Осталось придумать как его убить.
Как известно, настоящий джедай использует силу, а настоящий инженер использует мозг. Поэтому, идея тупо полностью отрубить найденный порт была мною отброшена —, а вдруг за этим портом сидит несколько пользователей и/или устройств? К счастью свитч-чип в MikroTik CRS позволяет делать MAC-Based-Vlan. Решение пришло тут же — выделить трафик от злодея по MAC и завернуть в отдельный неиспользуемый Vlan. В итоге, получился компактный скрипт, надежно отключающий от сети чужое устройство с поднятым на нём DHCP-сервером.
#dhcpsnooper - script to cut off rogue DHCP server
#stubvid - Stub Vlan Id to move rogue DHCP server. Set your own value according your vlan config
:local stubvid 666
:local smac ($"mac-address")
:local portname [/int eth sw uni get [find mac-address=$smac] port ]
:local imac [/int eth sw mac find src-mac-address=$smac]
if ([:len $imac]<1) do={/interface ethernet switch mac-based-vlan add new-customer-vid=($stubvid) src-mac-address=($smac) } else={
foreach i in $imac do={/interface ethernet switch mac-based-vlan set $i new-customer-vid=($stubvid) src-mac-address=($smac) disabled=no } }
/interface ethernet switch port set $portname allow-fdb-based-vlan-translate=yes
log error ("DHCP found on PORT:".($"portname")." MAC:".($"mac-address")." IP:".($address))
Переменная $stubvid, как должно быть понятно из названия — номер Vlan-заглушки. Переменная $imac — массив. MikroTik зачем-то позволяет делать несколько записей с одинаковым MAC в таблице mac-based-vlan, что вызывало сбой при попытке получить единственное значение. Предупреждения просто пишутся в log error, но ничто не мешает дописать более настойчивое информирование администратора сети.
Вызов алерта и скрипта у меня прописан следующим образом:
/ip dhcp-server alert add disabled=no interface=bridge-local on-alert=dhcpsnooper
Спасибо, что дочитали до конца. Надеюсь, вам это когда-нибудь пригодится.